summaryrefslogtreecommitdiff
path: root/SmartDeviceLink/SDLIAPDataSession.m
blob: 6e4d6bd1ef3f01cabd44afe6fd8c36f3ee2662e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//
//  SDLIAPDataSession.m
//  SmartDeviceLink
//
//  Created by Nicole on 4/17/19.
//  Copyright © 2019 smartdevicelink. All rights reserved.
//

#import "SDLIAPDataSession.h"

#import "SDLGlobals.h"
#import "SDLIAPConstants.h"
#import "SDLIAPSession.h"
#import "SDLLogMacros.h"
#import "SDLStreamDelegate.h"
#import "SDLIAPDataSessionDelegate.h"


NS_ASSUME_NONNULL_BEGIN

@interface SDLIAPDataSession ()

@property (nullable, strong, nonatomic, readwrite) SDLIAPSession *session;
@property (weak, nonatomic) id<SDLIAPDataSessionDelegate> delegate;

@end

@implementation SDLIAPDataSession

- (instancetype)initWithSession:(nullable SDLIAPSession *)session delegate:(id<SDLIAPDataSessionDelegate>)delegate {
    SDLLogV(@"SDLIAPDataSession init");

    self = [super init];
    if (!self) {
        return nil;
    }

    _session = session;
    _delegate = delegate;

    return self;
}

- (void)startSession {
    if (_session == nil) {
        SDLLogW(@"Failed to setup data session");
        if (self.delegate == nil) { return; }
        [self.delegate retryDataSession];
    } else {
        SDLLogD(@"Starting data session with accessory: %@, using protocol: %@", self.session.accessory.name, self.session.protocol);
        SDLStreamDelegate *ioStreamDelegate = [[SDLStreamDelegate alloc] init];
        self.session.streamDelegate = ioStreamDelegate;
        ioStreamDelegate.streamHasBytesHandler = [self sdl_dataStreamHasBytesHandler];
        ioStreamDelegate.streamEndHandler = [self sdl_dataStreamEndedHandler];
        ioStreamDelegate.streamErrorHandler = [self sdl_dataStreamErroredHandler];

        if (![self.session start]) {
            SDLLogW(@"Data session failed to setup with accessory: %@. Retrying...", self.session.accessory);
            [self stopSession];
            if (self.delegate == nil) { return; }
            [self.delegate retryDataSession];
        }
    }
}

- (void)stopSession {
    if (_session == nil) {
        SDLLogV(@"Attempting to stop the data session but the session is nil");
        return;
    }

    SDLLogD(@"Stopping the data session");
    [self.session stop]; // Calling stop but easession may not yet be set to `nil`
    self.session.streamDelegate = nil; //
    self.session = nil;
}

#pragma mark - Data Stream Handlers

/**
 *  Handler called when the session gets a `NSStreamEventEndEncountered` event code. The current session is closed and a new session is attempted.
 *
 *  @return A SDLStreamEndHandler handler
 */
- (SDLStreamEndHandler)sdl_dataStreamEndedHandler {
    __weak typeof(self) weakSelf = self;
    return ^(NSStream *stream) {
        NSAssert(!NSThread.isMainThread, @"%@ should only be called on the IO thread", NSStringFromSelector(_cmd));
        __strong typeof(weakSelf) strongSelf = weakSelf;

        SDLLogD(@"Data stream ended");
        if (strongSelf.session == nil) {
            SDLLogD(@"Data session is nil");
            return;
        }
        
        // The handler will be called on the IO thread, but the session stop method must be called on the main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            [strongSelf stopSession];

            if (self.delegate == nil) { return; }
            [self.delegate retryDataSession];
        });

        // To prevent deadlocks the handler must return to the runloop and not block the thread
    };
}

/**
 *  Handler called when the session gets a `NSStreamEventHasBytesAvailable` event code. The data is passed to the listener.
 *
 *  @return A SDLStreamHasBytesHandler handler
 */
- (SDLStreamHasBytesHandler)sdl_dataStreamHasBytesHandler {
    __weak typeof(self) weakSelf = self;
    return ^(NSInputStream *istream) {
        __strong typeof(weakSelf) strongSelf = weakSelf;

        NSAssert(!NSThread.isMainThread, @"%@ should only be called on the IO thread", NSStringFromSelector(_cmd));
        uint8_t buf[[[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC]];
        while (istream.streamStatus == NSStreamStatusOpen && istream.hasBytesAvailable) {
            // It is necessary to check the stream status and whether there are bytes available because the dataStreamHasBytesHandler is executed on the IO thread and the accessory disconnect notification arrives on the main thread, causing data to be passed to the delegate while the main thread is tearing down the transport.

            NSInteger bytesRead = [istream read:buf maxLength:[[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC]];
            if (bytesRead < 0) {
                SDLLogE(@"Failed to read from data stream");
                break;
            }

            NSData *dataIn = [NSData dataWithBytes:buf length:(NSUInteger)bytesRead];
            SDLLogBytes(dataIn, SDLLogBytesDirectionReceive);

            if (bytesRead > 0) {
                if (strongSelf.delegate == nil) { return; }
                [strongSelf.delegate dataReceived:dataIn];
            } else {
                break;
            }
        }
    };
}

/**
 *  Handler called when the session gets a `NSStreamEventErrorOccurred` event code. The current session is closed and a new session is attempted.
 *
 *  @return A SDLStreamErrorHandler handler
 */
- (SDLStreamErrorHandler)sdl_dataStreamErroredHandler {
    __weak typeof(self) weakSelf = self;
    return ^(NSStream *stream) {
        NSAssert(!NSThread.isMainThread, @"%@ should only be called on the IO thread", NSStringFromSelector(_cmd));
        __strong typeof(weakSelf) strongSelf = weakSelf;

        SDLLogE(@"Data stream error");
        dispatch_async(dispatch_get_main_queue(), ^{
            [strongSelf stopSession];
            if (![LegacyProtocolString isEqualToString:strongSelf.session.protocol]) {
                if (self.delegate == nil) { return; }
                [self.delegate retryDataSession];
            }
        });

        // To prevent deadlocks the handler must return to the runloop and not block the thread
    };
}

#pragma mark - Getters

- (NSUInteger)connectionID {
    return self.session.accessory.connectionID;
}

- (BOOL)isSessionInProgress {
    return (self.session != nil && !self.session.isStopped);
}

#pragma mark - Lifecycle Destruction

- (void)dealloc {
    SDLLogV(@"SDLIAPDataSession dealloc");
    _session = nil;
}

@end

NS_ASSUME_NONNULL_END