Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor IAP Transport / Fix EASessions left open #1910

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5de3bbb
refactor sdliapsession
makinney Jan 22, 2021
30774c8
refactor sdliaptransport
makinney Jan 22, 2021
5f8fd6a
refactor sdliapdatasession
makinney Jan 22, 2021
d81a887
refactor sdliapcontrolsession
makinney Jan 22, 2021
0d13f0e
update header api documentation
makinney Jan 28, 2021
19e4d9f
update SDLIAPSession unit tests
makinney Jan 28, 2021
58d9f05
update iapcontrolsession unit tests
makinney Jan 28, 2021
255fbdf
updarte iapdatasession unit tests
makinney Jan 28, 2021
aef11ac
ensure easession is always closed on easessions thread
makinney Feb 12, 2021
9d8931e
must wait until done to ensure eassesion closure on dealloc
makinney Feb 12, 2021
531ba7c
stop run loop before closing streams
makinney Feb 13, 2021
ef3617c
remove extraneous header include
makinney Feb 22, 2021
16e7d3f
prefix private methods with sdl remove spaces extrasemicolons and min…
makinney Feb 22, 2021
6951006
fix data session unit test
makinney Feb 22, 2021
d5de006
fix session unit test
makinney Feb 22, 2021
9379987
need fully mocked up eaacessory to test for easession operational status
makinney Feb 22, 2021
13949fd
update data session spec
makinney Feb 22, 2021
cdaf7f7
fix include file name
makinney Feb 22, 2021
4e78ffa
remove references to non existent selector from teransport spec
makinney Feb 22, 2021
9ecca06
fix include file extensions name
makinney Feb 22, 2021
d46ecda
update unnecessary iap transport tests since functionality moved into…
makinney Feb 22, 2021
918e39c
ensure byteswritten is not negative to prevent range crash
makinney Feb 23, 2021
0ce9ec6
update sdlisapsession class per style requirements
makinney Feb 23, 2021
e02a4f4
update sdlipadatasession to meet style requirements
makinney Feb 23, 2021
701517c
update per style requirements and remove unused function
makinney Feb 23, 2021
305ef31
update per style requirements
makinney Feb 23, 2021
e464609
several more style cleanups
makinney Feb 23, 2021
4464e00
comment should have SDLIAPDataSession
makinney Feb 23, 2021
f597491
minor formatting changes
makinney Feb 24, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
24 changes: 22 additions & 2 deletions SmartDeviceLink/private/SDLIAPControlSession.h
Expand Up @@ -21,7 +21,7 @@ NS_ASSUME_NONNULL_BEGIN
*
* When the protocol string is received from Core, the control session is closed as a new session with Core must be established with the received protocol string. Core has ~10 seconds to send the protocol string, otherwise the control session is closed and new attempt is made to establish a control session with Core.
*/
@interface SDLIAPControlSession : SDLIAPSession
@interface SDLIAPControlSession: NSObject <SDLIAPSessionDelegate>

- (instancetype)init NS_UNAVAILABLE;

Expand All @@ -32,7 +32,27 @@ NS_ASSUME_NONNULL_BEGIN
* @param delegate The control session delegate
* @return A SDLIAPControlSession object
*/
- (instancetype)initWithAccessory:(nullable EAAccessory *)accessory delegate:(id<SDLIAPControlSessionDelegate>)delegate;
- (instancetype)initWithAccessory:(nullable EAAccessory *)accessory delegate:(id<SDLIAPControlSessionDelegate>)delegate forProtocol:(NSString *)protocol;

/**
* Closes the SDLIAPSession used by the SDLIAPControlSession
*/
- (void) closeSession;
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved

/**
* Returns whether the session has open I/O streams.
*/
@property (assign, nonatomic, readonly, getter=isSessionInProgress) BOOL sessionInProgress;

/**
* The accessory used to create and the EASession.
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
*/
@property (nullable, strong, nonatomic, readonly) EAAccessory *accessory;

/**
* The unique ID assigned to the session between the app and accessory. If no session exists the value will be 0.
*/
@property (assign, nonatomic, readonly) NSUInteger connectionID;

@end

Expand Down
209 changes: 48 additions & 161 deletions SmartDeviceLink/private/SDLIAPControlSession.m
Expand Up @@ -22,206 +22,94 @@
int const ProtocolIndexTimeoutSeconds = 10;

@interface SDLIAPControlSession ()

@property (nullable, strong, nonatomic) SDLTimer *protocolIndexTimer;
@property (weak, nonatomic) id<SDLIAPControlSessionDelegate> delegate;
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved

@property (nullable, nonatomic, strong) SDLIAPSession *iapSession;
@end

@implementation SDLIAPControlSession

#pragma mark - Session lifecycle

- (instancetype)initWithAccessory:(nullable EAAccessory *)accessory delegate:(id<SDLIAPControlSessionDelegate>)delegate {
SDLLogV(@"SDLIAPControlSession init");

self = [super initWithAccessory:accessory forProtocol:ControlProtocolString];
if (!self) { return nil; }

- (instancetype)initWithAccessory:(nullable EAAccessory *)accessory delegate:(id<SDLIAPControlSessionDelegate>)delegate forProtocol:(NSString *)protocol {
SDLLogD(@"SDLIAPControlSession init with protocol %@ and accessory %@", protocol, accessory);
self = [super init];
_iapSession = [[SDLIAPSession alloc] initWithAccessory:accessory forProtocol:protocol iAPSessionDelegate:self];
_protocolIndexTimer = nil;
_delegate = delegate;
SDLLogD(@"SDLIAPControlSession Wait for the protocol string from Core, setting timeout timer for %d seconds", ProtocolIndexTimeoutSeconds);
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
self.protocolIndexTimer = [self sdl_createControlSessionProtocolIndexStringDataTimeoutTimer];

return self;
}

#pragma mark Start

- (void)startSession {
if (self.accessory == nil) {
SDLLogW(@"There is no control session in progress, attempting to create a new control session.");
[self.delegate controlSessionShouldRetry];
} else {
SDLLogD(@"Starting a control session with accessory (%@)", self.accessory.name);
__weak typeof(self) weakSelf = self;
[self sdl_startStreamsWithCompletionHandler:^(BOOL success) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!success) {
SDLLogW(@"Control session failed to setup with accessory: %@. Attempting to create a new control session", strongSelf.accessory);
[strongSelf destroySessionWithCompletionHandler:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.delegate controlSessionShouldRetry];
}];
} else {
SDLLogD(@"Waiting for the protocol string from Core, setting timeout timer for %d seconds", ProtocolIndexTimeoutSeconds);
strongSelf.protocolIndexTimer = [strongSelf sdl_createControlSessionProtocolIndexStringDataTimeoutTimer];
}
}];
}
- (void) closeSession {
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
[self.iapSession closeSession];
}

/// Opens the input and output streams for the session on the main thread.
/// @discussion We must close the input/output streams from the same thread that owns the streams' run loop, otherwise if the streams are closed from another thread a random crash may occur. Since only a small amount of data will be transmitted on this stream before it is closed, we will open and close the streams on the main thread instead of creating a separate thread.
- (void)sdl_startStreamsWithCompletionHandler:(void (^)(BOOL success))completionHandler {
if (![super createSession]) {
return completionHandler(NO);
}

__weak typeof(self) weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^{
__strong typeof(weakSelf) strongSelf = weakSelf;

SDLLogD(@"Created the control session successfully");
[super startStream:strongSelf.eaSession.outputStream];
[super startStream:strongSelf.eaSession.inputStream];

return completionHandler(YES);
});
- (nullable EAAccessory *) accessory {
return self.iapSession.accessory;
}

#pragma mark Stop

/// Makes sure the session is closed and destroyed on the main thread.
/// @param disconnectCompletionHandler Handler called when the session has disconnected
- (void)destroySessionWithCompletionHandler:(void (^)(void))disconnectCompletionHandler {
SDLLogD(@"Destroying the control session");
dispatch_async(dispatch_get_main_queue(), ^{
[self sdl_stopAndDestroySession];
return disconnectCompletionHandler();
});
- (NSUInteger)connectionID {
return self.iapSession.connectionID;
}

/// Closes the session streams and then destroys the session.
- (void)sdl_stopAndDestroySession {
NSAssert(NSThread.isMainThread, @"%@ must only be called on the main thread", NSStringFromSelector(_cmd));

[super stopStream:self.eaSession.outputStream];
[super stopStream:self.eaSession.inputStream];
[super cleanupClosedSession];
- (BOOL)isSessionInProgress {
return [self.iapSession isSessionInProgress];
}


#pragma mark - NSStreamDelegate

/**
* Handles events on the input/output streams of the open session.
*
* @param stream The stream (either input or output) that the event occured on
* @param eventCode The stream event code
*/
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventOpenCompleted: {
[self sdl_streamDidOpen:stream];
break;
}
case NSStreamEventHasBytesAvailable: {
[self sdl_streamHasBytesAvailable:(NSInputStream *)stream];
break;
}
case NSStreamEventErrorOccurred: {
[self sdl_streamDidError:stream];
break;
}
case NSStreamEventEndEncountered: {
[self sdl_streamDidEnd:stream];
break;
}
case NSStreamEventNone:
case NSStreamEventHasSpaceAvailable:
default: {
break;
}
}
}

/**
* Called when the session gets a `NSStreamEventOpenCompleted`. When both the input and output streams open, start a timer to get data from Core within a certain timeframe.
*
* @param stream The stream that got the event code.
*/
- (void)sdl_streamDidOpen:(NSStream *)stream {
if (stream == [self.eaSession outputStream]) {
SDLLogD(@"Control session output stream opened");
self.isOutputStreamOpen = YES;
} else if (stream == [self.eaSession inputStream]) {
SDLLogD(@"Control session input stream opened");
self.isInputStreamOpen = YES;
}

// When both streams are open, session initialization is complete. Let the delegate know.
if (self.isInputStreamOpen && self.isOutputStreamOpen) {
SDLLogV(@"Control session I/O streams opened for protocol: %@", self.protocolString);
- (void) streamsDidOpen {
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
SDLLogD(@"SDLIAPControlSession streams opened for control session instance %@", self);
if (self.delegate != nil) {
[self sdl_startControlSessionProtocolIndexStringDataTimeoutTimer];
}
}

/**
* Called when the session gets a `NSStreamEventEndEncountered` event code. The current session is closed and a new session is attempted.
*/
- (void)sdl_streamDidEnd:(NSStream *)stream {
SDLLogD(@"Control stream ended");

// End events come in pairs, only perform this once per set.
[self.protocolIndexTimer cancel];

__weak typeof(self) weakSelf = self;
[self destroySessionWithCompletionHandler:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.delegate controlSessionShouldRetry];
}];
- (void) streamsDidEnd {
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
SDLLogD(@"SDLIAPControlSession EASession stream ended");
if (self.delegate != nil) {
[self.delegate controlSessionDidEnd];
}
}

- (void)streamHasSpaceToWrite {}

/**
* Called when the session gets a `NSStreamEventHasBytesAvailable` event code. A protocol string is created from the received data. Since a new session needs to be established with the protocol string, the current session is closed and a new session is created.
*/
- (void)sdl_streamHasBytesAvailable:(NSInputStream *)inputStream {
SDLLogV(@"Control stream received data");

- (void)streamHasBytesAvailable:(NSInputStream *)inputStream {
SDLLogD(@"SDLIAPControlSession EASession stream received data");
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
// Read in the stream a single byte at a time
uint8_t buf[1];
NSInteger len = [inputStream read:buf maxLength:1];
if (len <= 0) {
SDLLogV(@"No data in the control stream");
SDLLogD(@"No data in the control stream");
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
return;
}

// If we have data from the control stream, use the data to create the protocol string needed to establish the data session.
// If we have data from the control stream, use the data to create the protocol string needed to establish a data session.
NSString *indexedProtocolString = [NSString stringWithFormat:@"%@%@", IndexedProtocolStringPrefix, @(buf[0])];
SDLLogD(@"Control Stream will switch to protocol %@", indexedProtocolString);

// Destroy the control session as it is no longer needed, and then create the data session.
__weak typeof(self) weakSelf = self;
[self destroySessionWithCompletionHandler:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf.accessory.isConnected) {
[strongSelf.protocolIndexTimer cancel];
[strongSelf.delegate controlSession:strongSelf didReceiveProtocolString:indexedProtocolString];
}
}];
SDLLogD(@"SDLIAPControlSession EASession Stream will switch to protocol %@", indexedProtocolString);

[self.protocolIndexTimer cancel];
if (self.delegate != nil) {
[self.delegate controlSession:self didReceiveProtocolString:indexedProtocolString];
}
}

/**
* Called when the session gets a `NSStreamEventErrorOccurred` event code. The current session is closed and a new session is attempted.
*/
- (void)sdl_streamDidError:(NSStream *)stream {
SDLLogE(@"Control stream error");

- (void)streamDidError {
SDLLogE(@"SDLIAPControlSession stream error");
[self.protocolIndexTimer cancel];
__weak typeof(self) weakSelf = self;
[self destroySessionWithCompletionHandler:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.delegate controlSessionShouldRetry];
}];
if (self.delegate != nil) {
[self.delegate controlSessionDidEnd];
}
}

#pragma mark - Timer
Expand All @@ -233,17 +121,14 @@ - (void)sdl_streamDidError:(NSStream *)stream {
*/
- (SDLTimer *)sdl_createControlSessionProtocolIndexStringDataTimeoutTimer {
SDLTimer *protocolIndexTimer = [[SDLTimer alloc] initWithDuration:ProtocolIndexTimeoutSeconds repeat:NO];

__weak typeof(self) weakSelf = self;
void (^elapsedBlock)(void) = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
SDLLogW(@"Control session failed to get the protocol string from Core after %d seconds, retrying.", ProtocolIndexTimeoutSeconds);
[strongSelf destroySessionWithCompletionHandler:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf.delegate controlSessionShouldRetry];
}];
SDLLogW(@"SDLIAPControlSession failed to get the protocol string from Core after %d seconds, retrying.", ProtocolIndexTimeoutSeconds);
if (self.delegate != nil) {
[strongSelf.delegate controlSessionDidEnd];
}
};

protocolIndexTimer.elapsedBlock = elapsedBlock;
return protocolIndexTimer;
}
Expand All @@ -259,3 +144,5 @@ - (void)sdl_startControlSessionProtocolIndexStringDataTimeoutTimer {
@end

NS_ASSUME_NONNULL_END


2 changes: 1 addition & 1 deletion SmartDeviceLink/private/SDLIAPControlSessionDelegate.h
Expand Up @@ -14,7 +14,7 @@ NS_ASSUME_NONNULL_BEGIN

@protocol SDLIAPControlSessionDelegate <NSObject>

- (void)controlSessionShouldRetry;
- (void)controlSessionDidEnd;
- (void)controlSession:(SDLIAPControlSession *)controlSession didReceiveProtocolString:(NSString *)protocolString;

@end
Expand Down
24 changes: 22 additions & 2 deletions SmartDeviceLink/private/SDLIAPDataSession.h
Expand Up @@ -7,18 +7,32 @@
//

#import <Foundation/Foundation.h>

#import "SDLIAPSession.h"

@protocol SDLIAPDataSessionDelegate;


NS_ASSUME_NONNULL_BEGIN

@interface SDLIAPDataSession : SDLIAPSession
@interface SDLIAPDataSession: NSObject <SDLIAPSessionDelegate>

- (instancetype)init NS_UNAVAILABLE;


NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
/**
* Returns whether the session has open I/O streams.
*/
@property (assign, nonatomic, readonly, getter=isSessionInProgress) BOOL sessionInProgress;

/**
* The unique ID assigned to the session between the app and accessory. If no session exists the value will be 0.
*/
@property (assign, nonatomic, readonly) NSUInteger connectionID;

/**
* The accessory with which to open a session.
*/
@property (nullable, strong, nonatomic, readonly) EAAccessory *accessory;
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
/**
* Creates a new data session.
*
Expand All @@ -28,6 +42,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (instancetype)initWithAccessory:(nullable EAAccessory *)accessory delegate:(id<SDLIAPDataSessionDelegate>)delegate forProtocol:(NSString *)protocol;

/**
* Closes the SDLIAPSession used by the SDLIAPControlSession
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* Closes the SDLIAPSession used by the SDLIAPControlSession
* Closes the SDLIAPSession used by the SDLIAPDataSession

*/
- (void) closeSession;
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved
NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved

/**
* Sends data to Core via the data session.
*
Expand All @@ -38,3 +57,4 @@ NS_ASSUME_NONNULL_BEGIN
@end

NS_ASSUME_NONNULL_END

NicoleYarroch marked this conversation as resolved.
Show resolved Hide resolved