diff --git a/SmartDeviceLink/private/SDLProtocol.m b/SmartDeviceLink/private/SDLProtocol.m index a2f8cc021..8f87bbdfb 100644 --- a/SmartDeviceLink/private/SDLProtocol.m +++ b/SmartDeviceLink/private/SDLProtocol.m @@ -34,7 +34,8 @@ #import "SDLV2ProtocolHeader.h" NSString *const SDLProtocolSecurityErrorDomain = @"com.sdl.protocol.security"; - +static const NSUInteger TLSMaxDataSize = 16834; +static const NSUInteger TLSMaxRPCPayloadDataToEncryptSize = 16384 /*TLS Max Record Size*/ - 5 /*TLS Record Header Size*/ - 32 /*TLS MES Auth CDE Size*/ - 256 /*TLS Max Record Padding Size*/; #pragma mark - SDLProtocol Private Interface @@ -82,7 +83,7 @@ - (instancetype)initWithTransport:(id)transport encryptionMana _transport.delegate = self; _encryptionLifecycleManager = encryptionManager; - + return self; } @@ -185,7 +186,7 @@ - (void)startSecureServiceWithType:(SDLServiceType)serviceType payload:(nullable // TLS initialization succeeded. Build and send the message. SDLProtocolMessage *message = [self sdl_createStartServiceMessageWithType:serviceType encrypted:YES payload:nil]; - SDLLogD(@"TLS initialized, sending start service for message: %@", message); + SDLLogD(@"TLS initialized, sending start service with encryption for message: %@", message); [self sdl_sendDataToTransport:message.data onService:serviceType]; }]; } @@ -309,7 +310,7 @@ - (BOOL)sendRPC:(SDLRPCMessage *)message encrypted:(BOOL)encryption error:(NSErr *error = jsonError; return NO; } - + NSData *messagePayload = nil; SDLLogV(@"Sending RPC: %@", message); @@ -346,27 +347,7 @@ - (BOOL)sendRPC:(SDLRPCMessage *)message encrypted:(BOOL)encryption error:(NSErr return NO; } - // If we're trying to encrypt, try to have the security manager encrypt it. Return if it fails. - NSError *encryptError = nil; - if (encryption) { - messagePayload = [self.securityManager encryptData:rpcPayload.data withError:&encryptError]; - - if (encryptError != nil) { - SDLLogE(@"Error encrypting request! %@", encryptError); - } - } else { - messagePayload = rpcPayload.data; - } - - // If the encryption failed, pass back the error and return false - if (!messagePayload) { - if (encryptError != nil) { - *error = encryptError; - } else { - *error = [NSError sdl_encryption_unknown]; - } - return NO; - } + messagePayload = rpcPayload.data; } break; default: { NSAssert(NO, @"Attempting to send an RPC based on an unknown version number: %@, message: %@", @([SDLGlobals sharedGlobals].protocolVersion.major), message); @@ -389,14 +370,57 @@ - (BOOL)sendRPC:(SDLRPCMessage *)message encrypted:(BOOL)encryption error:(NSErr SDLProtocolMessage *protocolMessage = [SDLProtocolMessage messageWithHeader:header andPayload:messagePayload]; // See if the message is small enough to send in one transmission. If not, break it up into smaller messages and send. - if (protocolMessage.size < [[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC]) { - SDLLogV(@"Sending protocol message: %@", protocolMessage); - [self sdl_sendDataToTransport:protocolMessage.data onService:SDLServiceTypeRPC]; + NSUInteger rpcMTUSize = [[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC]; + NSUInteger mtuSize = (encryption ? MIN(TLSMaxRPCPayloadDataToEncryptSize, rpcMTUSize) : rpcMTUSize); + NSArray *protocolMessages = nil; + if (protocolMessage.size < mtuSize) { + protocolMessages = @[protocolMessage]; } else { - NSArray *messages = [SDLProtocolMessageDisassembler disassemble:protocolMessage withLimit:[[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC]]; - for (SDLProtocolMessage *smallerMessage in messages) { - SDLLogV(@"Sending protocol message: %@", smallerMessage); - [self sdl_sendDataToTransport:smallerMessage.data onService:SDLServiceTypeRPC]; + protocolMessages = [SDLProtocolMessageDisassembler disassemble:protocolMessage withMTULimit:mtuSize]; + } + + // If the message should be encrypted, encrypt the payloads + if (encryption) { + BOOL success = [self sdl_encryptProtocolMessages:protocolMessages error:error]; + if (!success) { + SDLLogE(@"Error encrypting protocol messages. Messages will not be sent. Error: %@", *error); + return NO; + } + } + + // Send each message + for (SDLProtocolMessage *message in protocolMessages) { + SDLLogV(@"Sending protocol message: %@", message); + [self sdl_sendDataToTransport:message.data onService:SDLServiceTypeRPC]; + } + + return YES; +} + +/// Receives an array of `SDLProtocolMessage` and attempts to encrypt their payloads in place through the active security manager. If anything fails, it will return NO and pass back the error. +/// @param protocolMessages The array of protocol messages to encrypt. +/// @param error A passback error object if attempting to encrypt the protocol message payloads fails. +/// @returns YES if the encryption was successful, NO if it was not +- (BOOL)sdl_encryptProtocolMessages:(NSArray *)protocolMessages error:(NSError *__autoreleasing *)error { + for (SDLProtocolMessage *message in protocolMessages) { + if (message.header.frameType == SDLFrameTypeFirst) { continue; } + + // If we're trying to encrypt, try to have the security manager encrypt it. Return if it fails. + NSError *encryptError = nil; + NSData *encryptedMessagePayload = [self.securityManager encryptData:message.payload withError:&encryptError]; + + // If the encryption failed, pass back the error and return false + if (encryptedMessagePayload.length == 0 || encryptError != nil) { + if (encryptError != nil) { + *error = encryptError; + } else { + *error = [NSError sdl_encryption_unknown]; + } + + return NO; + } else { + message.payload = encryptedMessagePayload; + message.header.bytesInPayload = (UInt32)encryptedMessagePayload.length; } } @@ -418,7 +442,16 @@ - (void)sendRawData:(NSData *)data withServiceType:(SDLServiceType)serviceType { } - (void)sendEncryptedRawData:(NSData *)data onService:(SDLServiceType)serviceType { - [self sdl_sendRawData:data onService:serviceType encryption:YES]; + // Break up data larger than the max TLS size so the data can be encrypted by the security manager without failing due to the data size being too big + NSUInteger offset = 0; + do { + NSUInteger remainingDataLength = data.length - offset; + NSUInteger chunkSize = (remainingDataLength > TLSMaxDataSize) ? TLSMaxDataSize : remainingDataLength; + NSData *chunk = [NSData dataWithBytesNoCopy:(BytePtr)data.bytes + offset length:chunkSize freeWhenDone:NO]; + + [self sdl_sendRawData:chunk onService:serviceType encryption:YES]; + offset += chunkSize; + } while (offset < data.length); } - (void)sdl_sendRawData:(NSData *)data onService:(SDLServiceType)service encryption:(BOOL)encryption { @@ -444,7 +477,7 @@ - (void)sdl_sendRawData:(NSData *)data onService:(SDLServiceType)service encrypt SDLLogV(@"Sending protocol message: %@", message); [self sdl_sendDataToTransport:message.data onService:header.serviceType]; } else { - NSArray *messages = [SDLProtocolMessageDisassembler disassemble:message withLimit:[[SDLGlobals sharedGlobals] mtuSizeForServiceType:service]]; + NSArray *messages = [SDLProtocolMessageDisassembler disassemble:message withMTULimit:[[SDLGlobals sharedGlobals] mtuSizeForServiceType:service]]; for (SDLProtocolMessage *smallerMessage in messages) { SDLLogV(@"Sending protocol message: %@", smallerMessage); [self sdl_sendDataToTransport:smallerMessage.data onService:header.serviceType]; diff --git a/SmartDeviceLink/private/SDLProtocolMessage.m b/SmartDeviceLink/private/SDLProtocolMessage.m index 6ed86e05d..7e2da7d0c 100644 --- a/SmartDeviceLink/private/SDLProtocolMessage.m +++ b/SmartDeviceLink/private/SDLProtocolMessage.m @@ -55,7 +55,7 @@ - (NSString *)description { [description appendString:self.header.description]; if (self.header.encrypted) { - [description appendString:@", Payload is encrypted - no description can be provided"]; + [description appendString:@"Payload is encrypted - no description can be provided"]; return description; } diff --git a/SmartDeviceLink/private/SDLProtocolMessageAssembler.m b/SmartDeviceLink/private/SDLProtocolMessageAssembler.m index e02ead324..e4a07541c 100644 --- a/SmartDeviceLink/private/SDLProtocolMessageAssembler.m +++ b/SmartDeviceLink/private/SDLProtocolMessageAssembler.m @@ -32,13 +32,12 @@ - (void)handleMessage:(SDLProtocolMessage *)message withCompletionHandler:(SDLMe } if (self.parts == nil) { - self.parts = [NSMutableDictionary new]; + self.parts = [NSMutableDictionary dictionary]; } // Determine which frame it is and save it. - // Note: frames are numbered 1,2,3, ..., 0 - // Always 0 for last frame. + // Note: frames are numbered 1,2,3, ..., 0. Always 0 for last frame. if (message.header.frameType == SDLFrameTypeFirst) { // If it's the first-frame, extract the meta-data self.expectedBytes = NSSwapBigIntToHost(((UInt32 *)message.payload.bytes)[0]); @@ -51,27 +50,23 @@ - (void)handleMessage:(SDLProtocolMessage *)message withCompletionHandler:(SDLMe self.parts[frameNumberObj] = message.payload; } - - // // If we have all the parts, assemble it and execute the completion handler. - // SDLProtocolMessage *assembledMessage = nil; if (self.parts.count == self.frameCount + 1) { // +1 since we also require the first-frame - // Create the header SDLProtocolHeader *header = message.header.copy; header.frameType = SDLFrameTypeSingle; header.frameData = SDLFrameInfoSingleFrame; - // Create the payload - NSMutableData *payload = [[NSMutableData alloc] init]; + NSMutableData *payload = [NSMutableData data]; for (unsigned int i = 1; i < self.frameCount; i++) { - NSData *dataToAppend = [self.parts objectForKey:[NSNumber numberWithUnsignedInt:i]]; + NSData *dataToAppend = self.parts[@(i)]; [payload appendData:dataToAppend]; } + // Append the last frame, it has a frame # of 0. - NSData *dataToAppend = [self.parts objectForKey:[NSNumber numberWithUnsignedInt:0]]; + NSData *dataToAppend = self.parts[@0]; [payload appendData:dataToAppend]; // Validation @@ -82,7 +77,6 @@ - (void)handleMessage:(SDLProtocolMessage *)message withCompletionHandler:(SDLMe // Create the message. assembledMessage = [SDLProtocolMessage messageWithHeader:header andPayload:payload]; - // Execute completion handler. if (completionHandler != nil) { completionHandler(YES, assembledMessage); @@ -90,7 +84,6 @@ - (void)handleMessage:(SDLProtocolMessage *)message withCompletionHandler:(SDLMe // Done with this data, release it. self.parts = nil; - } else { // Not done, let caller know if (completionHandler != nil) { diff --git a/SmartDeviceLink/private/SDLProtocolMessageDisassembler.h b/SmartDeviceLink/private/SDLProtocolMessageDisassembler.h index 644b1c1de..8deabf8db 100644 --- a/SmartDeviceLink/private/SDLProtocolMessageDisassembler.h +++ b/SmartDeviceLink/private/SDLProtocolMessageDisassembler.h @@ -9,7 +9,11 @@ NS_ASSUME_NONNULL_BEGIN @interface SDLProtocolMessageDisassembler : NSObject -+ (NSArray *)disassemble:(SDLProtocolMessage *)protocolMessage withLimit:(NSUInteger)mtu; +/// Use to break up a large message into a sequence of smaller messages, each of which is less than 'mtu' number of bytes total size. +/// +/// @param protocolMessage The message to break up +/// @param mtu The MTU size to use to determine where to break up the message payload ++ (NSArray *)disassemble:(SDLProtocolMessage *)protocolMessage withMTULimit:(NSUInteger)mtu; @end diff --git a/SmartDeviceLink/private/SDLProtocolMessageDisassembler.m b/SmartDeviceLink/private/SDLProtocolMessageDisassembler.m index 21ab55f63..a8b77c806 100644 --- a/SmartDeviceLink/private/SDLProtocolMessageDisassembler.m +++ b/SmartDeviceLink/private/SDLProtocolMessageDisassembler.m @@ -9,25 +9,18 @@ @implementation SDLProtocolMessageDisassembler ++ (NSArray *)disassemble:(SDLProtocolMessage *)protocolMessage withMTULimit:(NSUInteger)mtu { + if (protocolMessage.size < mtu) { + return @[protocolMessage]; + } -// Use to break up a large message into a sequence of smaller messages, -// each of which is less than 'mtu' number of bytes total size. -+ (NSArray *)disassemble:(SDLProtocolMessage *)incomingMessage withLimit:(NSUInteger)mtu { - // Questions: - // What message IDs does the current system use? Same messageIDs? Same CorrelationIDs? - // What gets simply copied from incoming header to created headers; and what needs adjustment? - - // How big is the message header? - NSUInteger headerSize = incomingMessage.header.size; - - // The size of the message is too big to send in one chunk. - // So lets break it up. - // Just how big IS this message? - NSUInteger incomingPayloadSize = (incomingMessage.data.length - headerSize); + // How big is the message header and payload? + NSUInteger headerSize = protocolMessage.header.size; + NSUInteger payloadSize = (protocolMessage.data.length - headerSize); // How many messages do we need to create to hold that many bytes? - // Note: this does NOT count the special first message which acts as a descriptor. - NSUInteger numberOfMessagesRequired = (NSUInteger)ceil((float)incomingPayloadSize / (float)(mtu - headerSize)); + // Note: This does NOT count the special first message which acts as a descriptor. + NSUInteger numberOfMessagesRequired = (NSUInteger)ceilf((float)payloadSize / (float)(mtu - headerSize)); // And how many data bytes go in each message? NSUInteger numberOfDataBytesPerMessage = mtu - headerSize; @@ -35,48 +28,45 @@ @implementation SDLProtocolMessageDisassembler // Create the outgoing array to hold the messages we will create. NSMutableArray *outgoingMessages = [NSMutableArray arrayWithCapacity:numberOfMessagesRequired + 1]; - - // Create the first message - SDLProtocolHeader *firstFrameHeader = [incomingMessage.header copy]; + // Create the first message, which cannot be encrypted because it needs to be exactly 8 bytes + SDLProtocolHeader *firstFrameHeader = [protocolMessage.header copy]; firstFrameHeader.frameType = SDLFrameTypeFirst; + firstFrameHeader.encrypted = NO; UInt32 payloadData[2]; - payloadData[0] = CFSwapInt32HostToBig((UInt32)incomingMessage.payload.length); + payloadData[0] = CFSwapInt32HostToBig((UInt32)protocolMessage.payload.length); payloadData[1] = CFSwapInt32HostToBig((UInt32)numberOfMessagesRequired); NSMutableData *firstFramePayload = [NSMutableData dataWithBytes:payloadData length:sizeof(payloadData)]; SDLProtocolMessage *firstMessage = [SDLProtocolMessage messageWithHeader:firstFrameHeader andPayload:firstFramePayload]; - outgoingMessages[0] = firstMessage; - + [outgoingMessages addObject:firstMessage]; // Create the middle messages (the ones carrying the actual data). for (NSUInteger n = 0; n < numberOfMessagesRequired - 1; n++) { - // Frame # after 255 must cycle back to 1, not 0. - // A 0 signals last frame. + // Frame # after 255 must cycle back to 1, not 0. A 0 signals last frame (SDLFrameInfoConsecutiveLastFrame). UInt8 frameNumber = (n % 255) + 1; - SDLProtocolHeader *nextFrameHeader = [incomingMessage.header copy]; + SDLProtocolHeader *nextFrameHeader = [protocolMessage.header copy]; nextFrameHeader.frameType = SDLFrameTypeConsecutive; nextFrameHeader.frameData = frameNumber; NSUInteger offsetOfDataForThisFrame = headerSize + (n * numberOfDataBytesPerMessage); - NSData *nextFramePayload = [incomingMessage.data subdataWithRange:NSMakeRange(offsetOfDataForThisFrame, numberOfDataBytesPerMessage)]; + NSData *nextFramePayload = [protocolMessage.data subdataWithRange:NSMakeRange(offsetOfDataForThisFrame, numberOfDataBytesPerMessage)]; SDLProtocolMessage *nextMessage = [SDLProtocolMessage messageWithHeader:nextFrameHeader andPayload:nextFramePayload]; outgoingMessages[n + 1] = nextMessage; } - // Create the last message - SDLProtocolHeader *lastFrameHeader = [incomingMessage.header copy]; + SDLProtocolHeader *lastFrameHeader = [protocolMessage.header copy]; lastFrameHeader.frameType = SDLFrameTypeConsecutive; lastFrameHeader.frameData = SDLFrameInfoConsecutiveLastFrame; NSUInteger numberOfMessagesCreatedSoFar = numberOfMessagesRequired - 1; NSUInteger numberOfDataBytesSentSoFar = numberOfMessagesCreatedSoFar * numberOfDataBytesPerMessage; - NSUInteger numberOfDataBytesInLastMessage = incomingPayloadSize - numberOfDataBytesSentSoFar; + NSUInteger numberOfDataBytesInLastMessage = payloadSize - numberOfDataBytesSentSoFar; NSUInteger offsetOfDataForLastFrame = headerSize + numberOfDataBytesSentSoFar; - NSData *lastFramePayload = [incomingMessage.data subdataWithRange:NSMakeRange(offsetOfDataForLastFrame, numberOfDataBytesInLastMessage)]; + NSData *lastFramePayload = [protocolMessage.data subdataWithRange:NSMakeRange(offsetOfDataForLastFrame, numberOfDataBytesInLastMessage)]; SDLProtocolMessage *lastMessage = [SDLProtocolMessage messageWithHeader:lastFrameHeader andPayload:lastFramePayload]; outgoingMessages[numberOfMessagesRequired] = lastMessage; diff --git a/SmartDeviceLink/private/SDLV2ProtocolHeader.m b/SmartDeviceLink/private/SDLV2ProtocolHeader.m index d89fdeb1f..00567180c 100644 --- a/SmartDeviceLink/private/SDLV2ProtocolHeader.m +++ b/SmartDeviceLink/private/SDLV2ProtocolHeader.m @@ -109,7 +109,7 @@ - (NSString *)description { } NSMutableString *description = [[NSMutableString alloc] init]; - [description appendFormat:@"Version:%i, encrypted:%i, frameType:%@(%i), serviceType:%i, frameData:%@(%i), sessionID:%i, dataSize:%i, messageID:%i ", + [description appendFormat:@"Version:%i, encrypted:%i, frameType:%@(%i), serviceType:%i, frameData:%@(%i), sessionID:%i, dataSize:%i, messageID:%i, ", self.version, self.encrypted, frameTypeString, diff --git a/SmartDeviceLink/public/SDLProtocolConstants.h b/SmartDeviceLink/public/SDLProtocolConstants.h index 6da05c02e..b994ef437 100644 --- a/SmartDeviceLink/public/SDLProtocolConstants.h +++ b/SmartDeviceLink/public/SDLProtocolConstants.h @@ -94,5 +94,5 @@ typedef NS_ENUM(UInt8, SDLFrameInfo) { SDLFrameInfoFirstFrame = 0x00, // If frameType == First (0x02) /// Frame in a multiple frame payload. - SDLFrameInfoConsecutiveLastFrame = 0x00 // If frametype == Consecutive (0x03) + SDLFrameInfoConsecutiveLastFrame = 0x00 // If frameType == Consecutive (0x03) }; diff --git a/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m index 137006e6b..0dee191ef 100644 --- a/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m +++ b/SmartDeviceLinkTests/ProtocolSpecs/MessageSpecs/SDLProtocolSpec.m @@ -9,18 +9,22 @@ #import #import -#import "SDLTransportType.h" #import "SDLControlFramePayloadAudioStartServiceAck.h" #import "SDLControlFramePayloadRegisterSecondaryTransportNak.h" #import "SDLControlFramePayloadRPCStartServiceAck.h" #import "SDLControlFramePayloadVideoStartServiceAck.h" +#import "SDLDeleteCommand.h" +#import "SDLEncryptionLifecycleManager.h" #import "SDLGlobals.h" #import "SDLProtocolHeader.h" #import "SDLProtocol.h" #import "SDLProtocolMessage.h" +#import "SDLProtocolMessageDisassembler.h" #import "SDLProtocolReceivedMessageRouter.h" +#import "SDLRPCFunctionNames.h" #import "SDLRPCRequest.h" #import "SDLRPCParameterNames.h" +#import "SDLTransportType.h" #import "SDLV1ProtocolMessage.h" #import "SDLV2ProtocolMessage.h" #import "SDLV1ProtocolHeader.h" @@ -35,10 +39,12 @@ SDLRPCParameterNameCorrelationId:@0x98765, SDLRPCParameterNameParameters: @{SDLRPCParameterNameCommandId:@55}}}; -NSDictionary* dictionaryV2 = @{SDLRPCParameterNameCommandId:@55}; +// Send StartService Tests describe(@"Send StartService Tests", ^{ + // Insecure context(@"Insecure", ^{ + // Should send the correct data it(@"Should send the correct data", ^{ // Reset max protocol version before test. (This test case expects V1 header. If other test ran // prior to this one, SDLGlobals would keep the max protocol version and this test case would fail.) @@ -110,8 +116,11 @@ }); }); +// Send EndSession Tests describe(@"Send EndSession Tests", ^{ + // During V1 session context(@"During V1 session", ^{ + // Should send the correct data it(@"Should send the correct data", ^{ __block BOOL verified = NO; id transportMock = OCMProtocolMock(@protocol(SDLTransportType)); @@ -168,7 +177,9 @@ }); }); +// Send Register Secondary Transport Tests describe(@"Send Register Secondary Transport Tests", ^{ + // Should send the correct data it(@"Should send the correct data", ^{ __block BOOL verified = NO; id transportMock = OCMProtocolMock(@protocol(SDLTransportType)); @@ -201,13 +212,20 @@ }); }); -describe(@"SendRPCRequest Tests", ^{ +// SendRPC Tests +describe(@"SendRPC Tests", ^{ __block id mockRequest; beforeEach(^{ mockRequest = OCMPartialMock([[SDLRPCRequest alloc] init]); }); - + + afterEach(^{ + [SDLGlobals sharedGlobals].maxHeadUnitProtocolVersion = [SDLVersion versionWithMajor:1 minor:0 patch:0]; + }); + + // During V1 session context(@"During V1 session", ^{ + // Should send the correct data it(@"Should send the correct data", ^{ [[[[mockRequest stub] andReturn:dictionaryV1] ignoringNonObjectArgs] serializeAsDictionary:1]; @@ -245,42 +263,28 @@ expect(error).to(beNil()); }); }); - + + // During V2 session context(@"During V2 session", ^{ - it(@"Should send the correct data bulk data when bulk data is available", ^{ - [[[[mockRequest stub] andReturn:dictionaryV2] ignoringNonObjectArgs] serializeAsDictionary:2]; - [[[mockRequest stub] andReturn:@0x98765] correlationID]; - [[[mockRequest stub] andReturn:@"DeleteCommand"] name]; - [[[mockRequest stub] andReturn:[NSData dataWithBytes:"COMMAND" length:strlen("COMMAND")]] bulkData]; - - __block BOOL verified = NO; + beforeEach(^{ + [SDLGlobals sharedGlobals].maxHeadUnitProtocolVersion = [SDLVersion versionWithMajor:2 minor:0 patch:0]; + }); + + // Should send the correct data bulk data when bulk data is available + it(@"should correctly send a request smaller than the MTU size", ^{ + SDLDeleteCommand *deleteRequest = [[SDLDeleteCommand alloc] initWithId:55]; + deleteRequest.correlationID = @12345; + deleteRequest.bulkData = [NSData dataWithBytes:"COMMAND" length:strlen("COMMAND")]; + + __block NSUInteger numTimesCalled = 0; id transportMock = OCMProtocolMock(@protocol(SDLTransportType)); - [[[transportMock stub] andDo:^(NSInvocation* invocation) { - verified = YES; - - //Without the __unsafe_unretained, a double release will occur. More information: https://github.com/erikdoe/ocmock/issues/123 - __unsafe_unretained NSData* data; - [invocation getArgument:&data atIndex:2]; - NSData* dataSent = [data copy]; - - NSData* jsonTestData = [NSJSONSerialization dataWithJSONObject:dictionaryV2 options:0 error:0]; - NSUInteger dataLength = jsonTestData.length; - - const char testPayloadHeader[12] = {0x00, 0x00, 0x00, 0x06, 0x00, 0x09, 0x87, 0x65, (dataLength >> 24) & 0xFF, (dataLength >> 16) & 0xFF, (dataLength >> 8) & 0xFF, dataLength & 0xFF}; - - NSMutableData* payloadData = [NSMutableData dataWithBytes:testPayloadHeader length:12]; - [payloadData appendData:jsonTestData]; - [payloadData appendBytes:"COMMAND" length:strlen("COMMAND")]; - - const char testHeader[12] = {0x20 | SDLFrameTypeSingle, SDLServiceTypeBulkData, SDLFrameInfoSingleFrame, 0x01, (payloadData.length >> 24) & 0xFF, (payloadData.length >> 16) & 0xFF,(payloadData.length >> 8) & 0xFF, payloadData.length & 0xFF, 0x00, 0x00, 0x00, 0x01}; - - NSMutableData* testData = [NSMutableData dataWithBytes:testHeader length:12]; - [testData appendData:payloadData]; - - expect(dataSent).to(equal([testData copy])); + [[[transportMock stub] andDo:^(NSInvocation *invocation) { + numTimesCalled++; }] sendData:[OCMArg any]]; + SDLProtocol *testProtocol = [[SDLProtocol alloc] initWithTransport:transportMock encryptionManager:nil]; + // Send a start service ack to ensure that it's set up for sending the RPC SDLV2ProtocolHeader *testHeader = [[SDLV2ProtocolHeader alloc] initWithVersion:2]; testHeader.serviceType = SDLServiceTypeRPC; testHeader.sessionID = 0x01; @@ -289,14 +293,109 @@ NSError *error = nil; BOOL sent = [testProtocol sendRPC:mockRequest error:&error]; - expect(verified).toEventually(beTrue()); + expect(numTimesCalled).toEventually(equal(1)); expect(sent).to(beTrue()); expect(error).to(beNil()); + }); + + it(@"should correctly send a request larger than the MTU size", ^{ + char dummyBytes[1100]; + + SDLDeleteCommand *deleteRequest = [[SDLDeleteCommand alloc] initWithId:55]; + deleteRequest.correlationID = @12345; + deleteRequest.bulkData = [NSData dataWithBytes:dummyBytes length:1100]; + + __block NSUInteger numTimesCalled = 0; + id transportMock = OCMProtocolMock(@protocol(SDLTransportType)); + [[[transportMock stub] andDo:^(NSInvocation *invocation) { + numTimesCalled++; + }] sendData:[OCMArg any]]; + + SDLProtocol *testProtocol = [[SDLProtocol alloc] initWithTransport:transportMock encryptionManager:nil]; + + NSError *error = nil; + [SDLGlobals sharedGlobals].maxHeadUnitProtocolVersion = [SDLVersion versionWithMajor:2 minor:0 patch:0]; + BOOL sent = [testProtocol sendRPC:deleteRequest error:&error]; + + expect(numTimesCalled).toEventually(equal(3)); + expect(sent).to(beTrue()); + expect(error).to(beNil()); + }); + + describe(@"when encrypting the requests", ^{ + context(@"when the encryption manager is not ready", ^{ + it(@"should not send the request and return an error", ^{ + char dummyBytes[20000]; + + SDLDeleteCommand *deleteRequest = [[SDLDeleteCommand alloc] initWithId:55]; + deleteRequest.correlationID = @12345; + deleteRequest.bulkData = [NSData dataWithBytes:dummyBytes length:20000]; + deleteRequest.payloadProtected = YES; + + __block NSUInteger numTimesCalled = 0; + id transportMock = OCMProtocolMock(@protocol(SDLTransportType)); + [[[transportMock stub] andDo:^(NSInvocation *invocation) { + numTimesCalled++; + }] sendData:[OCMArg any]]; + + SDLProtocol *testProtocol = [[SDLProtocol alloc] initWithTransport:transportMock encryptionManager:nil]; + + NSError *error = nil; + [SDLGlobals sharedGlobals].maxHeadUnitProtocolVersion = [SDLVersion versionWithMajor:5 minor:0 patch:0]; + BOOL sent = [testProtocol sendRPC:deleteRequest error:&error]; + + expect(numTimesCalled).toEventually(equal(0)); + expect(sent).to(beFalse()); + expect(error).toNot(beNil()); + }); + }); + + context(@"when the encryption manager is ready", ^{ + __block NSUInteger numTimesCalled = 0; + __block SDLProtocol *testProtocol = nil; + NSUInteger dataSize = 20000; + beforeEach(^{ + id transportMock = OCMProtocolMock(@protocol(SDLTransportType)); + [[[transportMock stub] andDo:^(NSInvocation *invocation) { + numTimesCalled++; + }] sendData:[OCMArg any]]; + + SDLEncryptionLifecycleManager *encryptionMock = OCMClassMock([SDLEncryptionLifecycleManager class]); + OCMStub(encryptionMock.isEncryptionReady).andReturn(YES); + + id securityManager = OCMProtocolMock(@protocol(SDLSecurityType)); + char dummyBytes[dataSize]; + NSData *returnData = [NSData dataWithBytes:dummyBytes length:dataSize]; + OCMStub([securityManager encryptData:[OCMArg any] withError:[OCMArg setTo:nil]]).andReturn(returnData); + + testProtocol = [[SDLProtocol alloc] initWithTransport:transportMock encryptionManager:encryptionMock]; + testProtocol.securityManager = securityManager; + }); + + it(@"should correctly adjust the MTU size when the packet is encrypted and the service MTU size is larger than the TLS max size", ^{ + char dummyBytes[dataSize]; + + SDLDeleteCommand *deleteRequest = [[SDLDeleteCommand alloc] initWithId:55]; + deleteRequest.correlationID = @12345; + deleteRequest.bulkData = [NSData dataWithBytes:dummyBytes length:dataSize]; + deleteRequest.payloadProtected = YES; + + NSError *error = nil; + [SDLGlobals sharedGlobals].maxHeadUnitProtocolVersion = [SDLVersion versionWithMajor:5 minor:0 patch:0]; + BOOL sent = [testProtocol sendRPC:deleteRequest error:&error]; + + expect(numTimesCalled).toEventually(equal(3)); + expect(sent).to(beTrue()); + expect(error).to(beNil()); + }); + }); }); }); }); +// HandleBytesFromTransport Tests describe(@"HandleBytesFromTransport Tests", ^{ + // During V1 session context(@"During V1 session", ^{ // it(@"Should parse the data correctly", ^{ // id routerMock = OCMClassMock(SDLProtocolReceivedMessageRouter.class); @@ -343,7 +442,8 @@ // expect(@(verified)).toEventually(beTruthy()); // }); }); - + + // During V2 session context(@"During V2 session", ^{ // it(@"Should parse the data correctly", ^{ // id routerMock = OCMClassMock(SDLProtocolReceivedMessageRouter.class); @@ -403,6 +503,7 @@ }); }); +// HandleProtocolSessionStarted tests describe(@"HandleProtocolSessionStarted tests", ^{ __block id transportMock = nil; __block SDLProtocol *testProtocol = nil; @@ -427,7 +528,9 @@ #pragma clang diagnostic pop }); + // For protocol versions 5.0.0 and greater context(@"For protocol versions 5.0.0 and greater", ^{ + // If the service type is RPC context(@"If the service type is RPC", ^{ it(@"Should store the auth token, system info, and the protocol version and pass the start service along to the delegate", ^{ SDLControlFramePayloadRPCStartServiceAck *testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:hashId mtu:testMTU authToken:testAuthToken protocolVersion:@"5.2.0" secondaryTransports:nil audioServiceTransports:nil videoServiceTransports:nil make:testMake model:testModel trim:testTrim modelYear:testModelYear systemSoftwareVersion:testSystemSoftwareVersion systemHardwareVersion:testSystemHardwareVersion]; @@ -586,7 +689,9 @@ }); }); + // For protocol versions below 5.0.0 context(@"For protocol versions below 5.0.0", ^{ + // If the service type is RPC context(@"If the service type is RPC", ^{ it(@"Should store the protocol version and pass the start service along to the delegate", ^{ SDLControlFramePayloadRPCStartServiceAck *testPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:hashId mtu:testMTU authToken:nil protocolVersion:@"3.1.0" secondaryTransports:nil audioServiceTransports:nil videoServiceTransports:nil make:nil model:nil trim:nil modelYear:nil systemSoftwareVersion:nil systemHardwareVersion:nil]; @@ -691,6 +796,7 @@ }); }); +// HandleProtocolRegisterSecondaryTransport Tests describe(@"HandleProtocolRegisterSecondaryTransport Tests", ^{ __block id transportMock = nil; __block SDLProtocol *testProtocol = nil; @@ -700,6 +806,7 @@ testProtocol = [[SDLProtocol alloc] initWithTransport:transportMock encryptionManager:nil]; }); + // Should pass information along to delegate when ACKed it(@"Should pass information along to delegate when ACKed", ^{ id delegateMock = OCMProtocolMock(@protocol(SDLProtocolDelegate)); @@ -720,6 +827,7 @@ OCMVerifyAllWithDelay(delegateMock, 0.1); }); + // Should pass information along to delegate when NAKed it(@"Should pass information along to delegate when NAKed", ^{ id delegateMock = OCMProtocolMock(@protocol(SDLProtocolDelegate)); @@ -743,6 +851,7 @@ }); }); +// HandleHeartbeatForSession Tests describe(@"HandleHeartbeatForSession Tests", ^{ __block id transportMock = nil; __block SDLProtocol *testProtocol = nil; @@ -764,6 +873,7 @@ }); }); +// OnProtocolMessageReceived Tests describe(@"OnProtocolMessageReceived Tests", ^{ __block id transportMock = nil; __block SDLProtocol *testProtocol = nil; @@ -790,6 +900,7 @@ }); }); +// OnProtocolOpened Tests describe(@"OnProtocolOpened Tests", ^{ __block id transportMock = nil; __block SDLProtocol *testProtocol = nil; @@ -811,6 +922,7 @@ }); }); +// OnProtocolClosed Tests describe(@"OnProtocolClosed Tests", ^{ __block id transportMock = nil; __block SDLProtocol *testProtocol = nil; diff --git a/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolMessageDisassemblerSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolMessageDisassemblerSpec.m index 9c211ba05..a65652060 100644 --- a/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolMessageDisassemblerSpec.m +++ b/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolMessageDisassemblerSpec.m @@ -18,78 +18,106 @@ QuickSpecBegin(SDLProtocolMessageDisassemblerSpec) -describe(@"Disassemble Tests", ^ { - it(@"Should assemble the message properly", ^ { - //Allocate 2000 bytes, and use it as sample data - const NSUInteger dataLength = 2000; - char dummyBytes[dataLength]; - - SDLGlobals *globals = [[SDLGlobals alloc] init]; - globals.maxHeadUnitProtocolVersion = [SDLVersion versionWithString:@"2.0.0"]; - - const char testPayloadHeader[12] = {0x20, 0x55, 0x64, 0x73, 0x12, 0x34, 0x43, 0x21, (dataLength >> 24) & 0xFF, (dataLength >> 16) & 0xFF, (dataLength >> 8) & 0xFF, dataLength & 0xFF}; - - NSMutableData* payloadData = [NSMutableData dataWithBytes:testPayloadHeader length:12]; - [payloadData appendBytes:dummyBytes length:dataLength]; - - SDLV2ProtocolMessage* testMessage = [[SDLV2ProtocolMessage alloc] init]; - SDLV2ProtocolHeader* testHeader = [[SDLV2ProtocolHeader alloc] init]; - - testHeader.frameType = SDLFrameTypeSingle; - testHeader.serviceType = SDLServiceTypeBulkData; - testHeader.frameData = SDLFrameInfoSingleFrame; - testHeader.sessionID = 0x84; - testHeader.bytesInPayload = (UInt32)payloadData.length; - - testMessage.header = testHeader; - testMessage.payload = payloadData; - - NSArray *messageList = [SDLProtocolMessageDisassembler disassemble:testMessage withLimit:[globals mtuSizeForServiceType:testHeader.serviceType]]; - - //Payload length per message - UInt32 payloadLength = 1012; // v1/2 MTU(1024) - header length(12) - - const char firstPayload[8] = {(payloadData.length >> 24) & 0xFF, (payloadData.length >> 16) & 0xFF, (payloadData.length >> 8) & 0xFF, payloadData.length & 0xFF, 0x00, 0x00, 0x00, ceil(1.0 * payloadData.length / payloadLength)}; - - SDLProtocolMessage* message = messageList[0]; - - //First frame - expect(message.payload).to(equal([NSData dataWithBytes:firstPayload length:8])); - - expect(@(message.header.frameType)).to(equal(@(SDLFrameTypeFirst))); - expect(@(message.header.serviceType)).to(equal(@(SDLServiceTypeBulkData))); - expect(@(message.header.frameData)).to(equal(@(SDLFrameInfoFirstFrame))); - expect(@(message.header.sessionID)).to(equal(@0x84)); - expect(@(message.header.bytesInPayload)).to(equal(@8)); - - NSUInteger offset = 0; - for (int i = 1; i < messageList.count - 1; i++) { - message = messageList[i]; - - //Consecutive frames - expect(message.payload).to(equal([NSData dataWithData:[payloadData subdataWithRange:NSMakeRange(offset, payloadLength)]])); - +describe(@"SDLProtocolMessageDisassembler Tests", ^ { + context(@"when the MTU size is larger than the payload size", ^{ + it(@"should disassemble the message properly", ^{ + const NSUInteger dataLength = 400; + char dummyBytes[dataLength]; + + const char testPayloadHeader[12] = {0x20, 0x55, 0x64, 0x73, 0x12, 0x34, 0x43, 0x21, (dataLength >> 24) & 0xFF, (dataLength >> 16) & 0xFF, (dataLength >> 8) & 0xFF, dataLength & 0xFF}; + + NSMutableData* payloadData = [NSMutableData dataWithBytes:testPayloadHeader length:12]; + [payloadData appendBytes:dummyBytes length:dataLength]; + + SDLV2ProtocolMessage *testMessage = [[SDLV2ProtocolMessage alloc] init]; + SDLV2ProtocolHeader *testHeader = [[SDLV2ProtocolHeader alloc] init]; + + testHeader.frameType = SDLFrameTypeSingle; + testHeader.serviceType = SDLServiceTypeBulkData; + testHeader.frameData = SDLFrameInfoSingleFrame; + testHeader.sessionID = 0x84; + testHeader.bytesInPayload = (UInt32)payloadData.length; + + testMessage.header = testHeader; + testMessage.payload = payloadData; + + NSArray *messageList = [SDLProtocolMessageDisassembler disassemble:testMessage withMTULimit:1024]; + expect(messageList.count).to(equal(1)); + expect(messageList[0]).to(equal(testMessage)); + }); + }); + + context(@"when the MTU size is smaller than the payload size", ^{ + it(@"Should disassemble the message properly", ^ { + //Allocate 4000 bytes and use it as sample data + const NSUInteger dataLength = 4000; + char dummyBytes[dataLength]; + + const char testPayloadHeader[12] = {0x20, 0x55, 0x64, 0x73, 0x12, 0x34, 0x43, 0x21, (dataLength >> 24) & 0xFF, (dataLength >> 16) & 0xFF, (dataLength >> 8) & 0xFF, dataLength & 0xFF}; + + NSMutableData* payloadData = [NSMutableData dataWithBytes:testPayloadHeader length:12]; + [payloadData appendBytes:dummyBytes length:dataLength]; + + SDLV2ProtocolMessage* testMessage = [[SDLV2ProtocolMessage alloc] init]; + SDLV2ProtocolHeader* testHeader = [[SDLV2ProtocolHeader alloc] init]; + + testHeader.frameType = SDLFrameTypeSingle; + testHeader.serviceType = SDLServiceTypeBulkData; + testHeader.frameData = SDLFrameInfoSingleFrame; + testHeader.sessionID = 0x84; + testHeader.bytesInPayload = (UInt32)payloadData.length; + + testMessage.header = testHeader; + testMessage.payload = payloadData; + + NSArray *messageList = [SDLProtocolMessageDisassembler disassemble:testMessage withMTULimit:1024]; + expect(messageList.count).to(equal(5)); + + //Payload length per message + UInt32 payloadLength = 1012; // v1/2 MTU(1024) - header length(12) + + const char firstPayload[8] = {(payloadData.length >> 24) & 0xFF, (payloadData.length >> 16) & 0xFF, (payloadData.length >> 8) & 0xFF, payloadData.length & 0xFF, 0x00, 0x00, 0x00, ceil(1.0 * payloadData.length / payloadLength)}; + + SDLProtocolMessage* message = messageList[0]; + + //First frame + expect(message.payload).to(equal([NSData dataWithBytes:firstPayload length:8])); + + expect(@(message.header.frameType)).to(equal(@(SDLFrameTypeFirst))); + expect(@(message.header.serviceType)).to(equal(@(SDLServiceTypeBulkData))); + expect(@(message.header.frameData)).to(equal(@(SDLFrameInfoFirstFrame))); + expect(@(message.header.sessionID)).to(equal(@0x84)); + expect(@(message.header.bytesInPayload)).to(equal(@8)); + + NSUInteger offset = 0; + for (int i = 1; i < messageList.count - 1; i++) { + message = messageList[i]; + + //Consecutive frames + expect(message.payload).to(equal([NSData dataWithData:[payloadData subdataWithRange:NSMakeRange(offset, payloadLength)]])); + + expect(@(message.header.frameType)).to(equal(@(SDLFrameTypeConsecutive))); + expect(@(message.header.serviceType)).to(equal(@(SDLServiceTypeBulkData))); + expect(@(message.header.frameData)).to(equal(@(i))); + expect(@(message.header.sessionID)).to(equal(@0x84)); + expect(@(message.header.bytesInPayload)).to(equal(@(payloadLength))); + + offset += payloadLength; + } + + message = [messageList lastObject]; + + NSUInteger remaining = payloadData.length - offset; + + //Last frame + expect(message.payload).to(equal([NSData dataWithData:[payloadData subdataWithRange:NSMakeRange(offset, remaining)]])); + expect(@(message.header.frameType)).to(equal(@(SDLFrameTypeConsecutive))); expect(@(message.header.serviceType)).to(equal(@(SDLServiceTypeBulkData))); - expect(@(message.header.frameData)).to(equal(@(i))); + expect(@(message.header.frameData)).to(equal(@(SDLFrameInfoConsecutiveLastFrame))); expect(@(message.header.sessionID)).to(equal(@0x84)); - expect(@(message.header.bytesInPayload)).to(equal(@(payloadLength))); - - offset += payloadLength; - } - - message = [messageList lastObject]; - - NSUInteger remaining = payloadData.length - offset; - - //Last frame - expect(message.payload).to(equal([NSData dataWithData:[payloadData subdataWithRange:NSMakeRange(offset, remaining)]])); - - expect(@(message.header.frameType)).to(equal(@(SDLFrameTypeConsecutive))); - expect(@(message.header.serviceType)).to(equal(@(SDLServiceTypeBulkData))); - expect(@(message.header.frameData)).to(equal(@(SDLFrameInfoConsecutiveLastFrame))); - expect(@(message.header.sessionID)).to(equal(@0x84)); - expect(@(message.header.bytesInPayload)).to(equal(@(remaining))); + expect(@(message.header.bytesInPayload)).to(equal(@(remaining))); + }); }); }); diff --git a/generator/rpc_spec b/generator/rpc_spec index 72632f946..6b9835535 160000 --- a/generator/rpc_spec +++ b/generator/rpc_spec @@ -1 +1 @@ -Subproject commit 72632f946941d63a57ee5e99896e3eae3627f7dd +Subproject commit 6b98355357b5b1893bbb59cb668d28545023457c