summaryrefslogtreecommitdiff
path: root/SmartDeviceLink/private/SDLLifecycleProtocolHandler.m
blob: 384af7d863e09863b8518953b4d41f02bad33e3d (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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
//
//  SDLProtocolDelegateHandler.m
//  SmartDeviceLink
//
//  Created by Joel Fischer on 6/10/20.
//  Copyright © 2020 smartdevicelink. All rights reserved.
//

#import "SDLLifecycleProtocolHandler.h"

#import "SDLConfiguration.h"
#import "SDLControlFramePayloadRPCStartService.h"
#import "SDLError.h"
#import "SDLGlobals.h"
#import "SDLLifecycleConfiguration.h"
#import "SDLLifecycleRPCAdapter.h"
#import "SDLLogMacros.h"
#import "SDLNotificationConstants.h"
#import "SDLNotificationDispatcher.h"
#import "SDLProtocol.h"
#import "SDLProtocolHeader.h"
#import "SDLProtocolMessage.h"
#import "SDLRPCFunctionNames.h"
#import "SDLRPCMessage.h"
#import "SDLRPCMessageType.h"
#import "SDLTimer.h"

static const float StartSessionTime = 10.0;
int const RPCStartServiceRetries = 2;

NS_ASSUME_NONNULL_BEGIN

@interface SDLLifecycleProtocolHandler ()

@property (weak, nonatomic) SDLNotificationDispatcher *notificationDispatcher;

@property (strong, nonatomic, nullable) SDLTimer *rpcStartServiceTimeoutTimer;
@property (assign, nonatomic) int rpcStartServiceRetryCounter;
@property (copy, nonatomic) NSString *appId;


@end

@implementation SDLLifecycleProtocolHandler

- (instancetype)initWithProtocol:(SDLProtocol *)protocol notificationDispatcher:(SDLNotificationDispatcher *)notificationDispatcher configuration:(SDLConfiguration *)configuration {
    self = [super init];
    if (!self) { return nil; }

    _rpcStartServiceRetryCounter = 0;
    _protocol = protocol;
    _notificationDispatcher = notificationDispatcher;

    _appId = configuration.lifecycleConfig.fullAppId ? configuration.lifecycleConfig.fullAppId : configuration.lifecycleConfig.appId;
    _protocol.appId = _appId;

    [_protocol.protocolDelegateTable addObject:self];

    return self;
}

- (void)start {
    [self.protocol start];
}

- (void)stopWithCompletionHandler:(void (^)(void))disconnectCompletionHandler {
    [self.protocol stopWithCompletionHandler:^{
        disconnectCompletionHandler();
    }];
}


#pragma mark - SDLProtocolDelegate

/// Called when the transport is opened. We will send the RPC Start Service and wait for the RPC Start Service ACK
- (void)protocolDidOpen:(SDLProtocol *)protocol {
    if (self.protocol != protocol) { return; }

    SDLLogD(@"Transport opened.");
    [self.notificationDispatcher postNotificationName:SDLTransportDidConnect infoObject:nil];

    [self sdl_sendStartServiceSession:self.rpcStartServiceRetryCounter];
}

/// Sends the RPC start service request to the module and sets a timer to wait for a response from the module. If the module ACKs or NAKs the request, then the timer is canceled. However, if the module does not respond within a set amount of time, the RPC start service is resent. Once the max number of retry accounts has been reached the session is closed.
/// @param retryCount The retry attempt count
- (void)sdl_sendStartServiceSession:(int)retryCount {
    if (retryCount > RPCStartServiceRetries) {
        SDLLogE(@"Retrying sending the RPC start service failed %d times. Closing the session", RPCStartServiceRetries);
        return [self.protocol stopWithCompletionHandler:^{}];
    }

    SDLControlFramePayloadRPCStartService *startServicePayload = [[SDLControlFramePayloadRPCStartService alloc] initWithVersion:SDLMaxProxyProtocolVersion];
    [self.protocol startServiceWithType:SDLServiceTypeRPC payload:startServicePayload.data];

    if (self.rpcStartServiceTimeoutTimer != nil) {
        [self.rpcStartServiceTimeoutTimer cancel];
        self.rpcStartServiceTimeoutTimer = nil;
    }

    self.rpcStartServiceTimeoutTimer = [[SDLTimer alloc] initWithDuration:StartSessionTime repeat:NO];
    __weak typeof(self) weakSelf = self;
    self.rpcStartServiceTimeoutTimer.elapsedBlock = ^{
        weakSelf.rpcStartServiceRetryCounter += 1;
        SDLLogE(@"Module did not respond to the RPC start service within %.f seconds. Retrying sending the RPC start service (#%d)", StartSessionTime, weakSelf.rpcStartServiceRetryCounter);
        [weakSelf sdl_sendStartServiceSession:weakSelf.rpcStartServiceRetryCounter];
    };

    SDLLogD(@"Starting timeout timer for the module's response to the RPC start service");
    [self.rpcStartServiceTimeoutTimer start];
}

/// Called when the transport is closed.
- (void)protocolDidClose:(SDLProtocol *)protocol {
    if (self.protocol != protocol) { return; }

    SDLLogW(@"Transport disconnected");
    [self.notificationDispatcher postNotificationName:SDLTransportDidDisconnect infoObject:nil];
}

- (void)protocol:(SDLProtocol *)protocol transportDidError:(NSError *)error {
    if (self.protocol != protocol) { return; }

    SDLLogW(@"Transport error: %@", error);
    [self.notificationDispatcher postNotificationName:SDLTransportConnectError infoObject:error];
}

- (void)protocol:(SDLProtocol *)protocol didReceiveStartServiceACK:(SDLProtocolMessage *)startServiceACK {
    if (self.protocol != protocol) { return; }

    SDLLogD(@"Start Service (ACK) SessionId: %d for serviceType %d", startServiceACK.header.sessionID, startServiceACK.header.serviceType);

    if (startServiceACK.header.serviceType == SDLServiceTypeRPC) {
        [self.rpcStartServiceTimeoutTimer cancel];
        [self.notificationDispatcher postNotificationName:SDLRPCServiceDidConnect infoObject:nil];
    }
}

- (void)protocol:(SDLProtocol *)protocol didReceiveStartServiceNAK:(SDLProtocolMessage *)startServiceNAK {
    if (self.protocol != protocol) { return; }

    SDLLogD(@"Start Service (NAK): SessionId: %d for serviceType %d", startServiceNAK.header.sessionID, startServiceNAK.header.serviceType);

    if (startServiceNAK.header.serviceType == SDLServiceTypeRPC) {
        [self.rpcStartServiceTimeoutTimer cancel];
        [self.notificationDispatcher postNotificationName:SDLRPCServiceConnectionDidError infoObject:nil];
    }
}

- (void)protocol:(SDLProtocol *)protocol didReceiveEndServiceACK:(SDLProtocolMessage *)endServiceACK {
    if (self.protocol != protocol) { return; }

    SDLLogD(@"End Service (ACK): SessionId: %d for serviceType %d", endServiceACK.header.sessionID, endServiceACK.header.serviceType);

    if (endServiceACK.header.serviceType == SDLServiceTypeRPC) {
        [self.rpcStartServiceTimeoutTimer cancel];
        [self.notificationDispatcher postNotificationName:SDLRPCServiceDidDisconnect infoObject:nil];
    }
}

- (void)protocol:(SDLProtocol *)protocol didReceiveEndServiceNAK:(SDLProtocolMessage *)endServiceNAK {
    if (self.protocol != protocol) { return; }

    if (endServiceNAK.header.serviceType == SDLServiceTypeRPC) {
        NSError *error = [NSError sdl_lifecycle_unknownRemoteErrorWithDescription:@"RPC Service failed to stop" andReason:nil];
        [self.notificationDispatcher postNotificationName:SDLRPCServiceConnectionDidError infoObject:error];
    }
}

- (void)protocol:(SDLProtocol *)protocol didReceiveMessage:(SDLProtocolMessage *)msg {
    if (self.protocol != protocol) { return; }

    NSDictionary<NSString *, id> *rpcMessageAsDictionary = [msg rpcDictionary];
    SDLRPCMessage *receivedMessage = [[SDLRPCMessage alloc] initWithDictionary:rpcMessageAsDictionary];
    NSString *fullName = [self sdl_fullNameForMessage:receivedMessage];

    // From the function name, create the corresponding RPCObject and initialize it
    NSString *functionClassName = [NSString stringWithFormat:@"SDL%@", fullName];
    SDLRPCMessage *newMessage = [[NSClassFromString(functionClassName) alloc] initWithDictionary:rpcMessageAsDictionary];

    // If we were unable to create the message, it's an unknown type; discard it
    if (newMessage == nil) {
        SDLLogE(@"Unable to create message for RPC: %@", rpcMessageAsDictionary);
        return;
    }

    // Adapt the incoming message then call the callback
    NSArray<SDLRPCMessage *> *adaptedMessages = [SDLLifecycleRPCAdapter adaptRPC:newMessage direction:SDLRPCDirectionIncoming];
    for (SDLRPCMessage *message in adaptedMessages) {
        [self sdl_sendCallbackForMessage:message];
    }
}

#pragma mark - Utilities

- (void)sdl_sendCallbackForMessage:(SDLRPCMessage *)message {
    // Log the RPC message
    SDLLogV(@"Sending callback for RPC message: %@", message);

    SDLNotificationName notificationName = [self sdl_notificationNameForMessage:message];
    if ([message.messageType isEqualToEnum:SDLRPCMessageTypeNameResponse]) {
        [self.notificationDispatcher postRPCResponseNotification:notificationName response:(SDLRPCResponse *)message];
    } else if ([message.messageType isEqualToEnum:SDLRPCMessageTypeNameRequest]) {
        [self.notificationDispatcher postRPCRequestNotification:notificationName request:(SDLRPCRequest *)message];
    } else if ([message.messageType isEqualToEnum:SDLRPCMessageTypeNameNotification]) {
        [self.notificationDispatcher postRPCNotificationNotification:notificationName notification:(SDLRPCNotification *)message];
    }
}

- (SDLNotificationName)sdl_notificationNameForMessage:(SDLRPCMessage *)message {
    NSString *messageName = message.name;
    if ([message.messageType isEqualToEnum:SDLRPCMessageTypeNameResponse]) {
        return [NSString stringWithFormat:@"com.sdl.response.%@", messageName];
    } else if ([message.messageType isEqualToEnum:SDLRPCMessageTypeNameRequest]) {
        return [NSString stringWithFormat:@"com.sdl.request.%@", messageName];
    } else if ([message.messageType isEqualToEnum:SDLRPCMessageTypeNameNotification]) {
        return [NSString stringWithFormat:@"com.sdl.notification.%@", messageName];
    }

    return messageName;
}

- (NSString *)sdl_fullNameForMessage:(SDLRPCMessage *)message {
    NSString *functionName = message.name;
    NSString *messageType = message.messageType;

    // If it's a response, append "response"
    if ([messageType isEqualToEnum:SDLRPCMessageTypeNameResponse]) {
        if (![functionName isEqualToEnum:SDLRPCFunctionNameGenericResponse]) {
            functionName = [NSString stringWithFormat:@"%@Response", functionName];
        }
    }

    return functionName;
}

@end

NS_ASSUME_NONNULL_END