summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2022-09-02 08:48:28 -0400
committerGitHub <noreply@github.com>2022-09-02 08:48:28 -0400
commit74190b50263bb329c928203cdaec68e309d4e1ea (patch)
tree15875b1dc2b6d562a04dd1b3eb2ba80293a1bdd3
parentaba5ba83d902a1c5e7e68915180eaae319855430 (diff)
parent1d50cc2f21bcd1dbbe2b810e058c757a1f516c96 (diff)
downloadsdl_ios-74190b50263bb329c928203cdaec68e309d4e1ea.tar.gz
Merge pull request #2100 from smartdevicelink/issue-17-SDLProtocol-message-parsing
Update Protocol Message Parsing to Use a State Machine
-rw-r--r--.github/CONTRIBUTING.md10
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj12
-rw-r--r--SmartDeviceLink/private/SDLProtocol.m73
-rw-r--r--SmartDeviceLink/private/SDLProtocolHeader.m13
-rw-r--r--SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.h31
-rw-r--r--SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.m290
-rw-r--r--SmartDeviceLink/private/SDLV1ProtocolHeader.m5
-rw-r--r--SmartDeviceLink/private/SDLV2ProtocolHeader.m10
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m38
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m17
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m17
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolReceivedMessageProcessorSpec.m769
12 files changed, 1222 insertions, 63 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index a9e617d4f..189e8a039 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -12,12 +12,12 @@ We use [Gitflow](http://nvie.com/posts/a-successful-git-branching-model/) as our
### Pull Requests
* Please follow the repository's for all code and documentation.
-* All feature branches should be based on `develop` and have the format `feature/issue-#num-branch_name`.
-* Minor bug fixes, that is bug fixes that do not change, add, or remove any public API, should be based on `develop` and have the format `bugfix/issue-#num-branch_name`, unless they are slated for a hotfix release, in which case they should be based on `master`.
-* All pull requests should implement a single feature or fix a single bug related to an open issue. Pull Requests that involve multiple changes (it is our discretion what precisely this means) will be rejected with a reason.
+* All feature branches should be based on `develop` and have the format `feature/branch_name`.
+* Minor bug fixes, that is bug fixes that do not change, add, or remove any public API, should be based on `master` and have the format `hotfix/branch_name`.
+* All pull requests should implement a single feature or fix a single bug. Pull Requests that involve multiple changes (it is our discretion what precisely this means) will be rejected with a reason.
* All commits should separated into logical units, i.e. unrelated changes should be in different commits within a pull request.
-* Work in progress pull requests should be Draft PRs. When you believe the pull request is ready to merge, mark them as ready for review to make them an open PR and @mention the appropriate SDL team to schedule a review.
-* All new code *must* include unit tests. Bug fixes should have a test that failed previously and now passes. All new features should have test coverage. If your code does not have tests, or regresses old tests, it will be rejected.
+* Work in progress pull requests should have "[WIP]" in front of the Pull Request title. When you believe the pull request is ready to merge, remove this tag and @mention the appropriate SDL team to schedule a review.
+* All new code *must* include unit tests. Bug fixes should have a test that fails previously and now passes. All new features should be covered. If your code does not have tests, or regresses old tests, it will be rejected.
* Make sure you fill out all sections of the PR template. A great example of a [pull request can be found here](https://github.com/smartdevicelink/sdl_ios/pull/1688).
### Contributor's License Agreement (CLA)
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index ddaeb9a4c..17dd7048b 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -1789,6 +1789,9 @@
EEB2537E2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EEB2537D2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m */; };
EED5CA041F4D1D5E00F04000 /* SDLRAWH264PacketizerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EED5CA031F4D1D5E00F04000 /* SDLRAWH264PacketizerSpec.m */; };
EED5CA0A1F4D206800F04000 /* SDLRTPH264PacketizerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EED5CA091F4D206800F04000 /* SDLRTPH264PacketizerSpec.m */; };
+ EF44EC2328A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EF44EC2228A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m */; };
+ EFEBDC9E287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = EFEBDC9D287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m */; };
+ EFEBDCA0287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = EFEBDC9F287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -3715,6 +3718,9 @@
EEB2537D2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLSecondaryTransportManagerSpec.m; path = ProxySpecs/SDLSecondaryTransportManagerSpec.m; sourceTree = "<group>"; };
EED5CA031F4D1D5E00F04000 /* SDLRAWH264PacketizerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLRAWH264PacketizerSpec.m; path = DevAPISpecs/SDLRAWH264PacketizerSpec.m; sourceTree = "<group>"; };
EED5CA091F4D206800F04000 /* SDLRTPH264PacketizerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLRTPH264PacketizerSpec.m; path = DevAPISpecs/SDLRTPH264PacketizerSpec.m; sourceTree = "<group>"; };
+ EF44EC2228A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLProtocolReceivedMessageProcessorSpec.m; sourceTree = "<group>"; };
+ EFEBDC9D287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLProtocolReceivedMessageProcessor.m; path = private/SDLProtocolReceivedMessageProcessor.m; sourceTree = "<group>"; };
+ EFEBDC9F287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLProtocolReceivedMessageProcessor.h; path = private/SDLProtocolReceivedMessageProcessor.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -4303,6 +4309,7 @@
1680B10B1A9CD7AD00DBD79E /* SDLProtocolSpec.m */,
1680B1101A9CD7AD00DBD79E /* SDLProtocolMessageAssemblerSpec.m */,
1680B1111A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m */,
+ EF44EC2228A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m */,
1680B1121A9CD7AD00DBD79E /* SDLProtocolReceivedMessageRouterSpec.m */,
);
path = ProtocolSpecs;
@@ -4930,6 +4937,8 @@
4A8BD36924F94636000945E3 /* SDLProtocolMessageAssembler.m */,
4A8BD36724F94636000945E3 /* SDLProtocolMessageDisassembler.h */,
4A8BD36824F94636000945E3 /* SDLProtocolMessageDisassembler.m */,
+ EFEBDC9F287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h */,
+ EFEBDC9D287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m */,
4A8BD37124F9465E000945E3 /* SDLProtocolReceivedMessageRouter.h */,
4A8BD37024F9465E000945E3 /* SDLProtocolReceivedMessageRouter.m */,
);
@@ -7549,6 +7558,7 @@
B3A9DB2025D4BBB800CDFD21 /* SDLVideoStreamingCapability+StreamingVideoExtensions.h in Headers */,
4ABB257724F7E5E80061BF55 /* SDLDeleteChoicesOperation.h in Headers */,
4ABB24C124F592620061BF55 /* NSMapTable+Subscripting.h in Headers */,
+ EFEBDCA0287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h in Headers */,
4ABB2A2C24F847980061BF55 /* SDLChangeRegistrationResponse.h in Headers */,
4ABB264024F7F45B0061BF55 /* SDLSystemCapabilityObserver.h in Headers */,
4ABB27E524F800CA0061BF55 /* SDLPrimaryAudioSource.h in Headers */,
@@ -8641,6 +8651,7 @@
5D9FDA911F2A7D3400A495C8 /* bson_object.c in Sources */,
4A8BD26024F933C7000945E3 /* SDLNavigationCapability.m in Sources */,
4ABB29E024F846880061BF55 /* SDLSubscribeWayPoints.m in Sources */,
+ EFEBDC9E287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m in Sources */,
4ABB2B5824F84EF50061BF55 /* SDLButtonCapabilities.m in Sources */,
4A8BD32C24F9431B000945E3 /* SDLV2ProtocolHeader.m in Sources */,
4ABB2B4724F84EF50061BF55 /* SDLBeltStatus.m in Sources */,
@@ -8902,6 +8913,7 @@
1680B1141A9CD7AD00DBD79E /* SDLV1ProtocolHeaderSpec.m in Sources */,
B3DF19F3251225AA0090D7BA /* TestSmartConnection.m in Sources */,
B3838A15257C4EFD00420C11 /* SDLRoofStatusSpec.m in Sources */,
+ EF44EC2328A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m in Sources */,
880D2680220E038800B3F496 /* SDLWeatherServiceManifestSpec.m in Sources */,
88EEC5BE220A3B8B005AA2F9 /* SDLPublishAppServiceResponseSpec.m in Sources */,
1680B1161A9CD7AD00DBD79E /* SDLProtocolMessageSpec.m in Sources */,
diff --git a/SmartDeviceLink/private/SDLProtocol.m b/SmartDeviceLink/private/SDLProtocol.m
index 7ea872f18..676243add 100644
--- a/SmartDeviceLink/private/SDLProtocol.m
+++ b/SmartDeviceLink/private/SDLProtocol.m
@@ -22,6 +22,7 @@
#import "SDLProtocolHeader.h"
#import "SDLProtocolMessage.h"
#import "SDLProtocolMessageDisassembler.h"
+#import "SDLProtocolReceivedMessageProcessor.h"
#import "SDLProtocolReceivedMessageRouter.h"
#import "SDLRPCNotification.h"
#import "SDLRPCPayload.h"
@@ -50,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
SDLPrioritizedObjectCollection *_prioritizedCollection;
}
+@property (nullable, strong, nonatomic) SDLProtocolReceivedMessageProcessor *receiveProcessor;
@property (strong, nonatomic) NSMutableData *receiveBuffer;
@property (nullable, strong, nonatomic) SDLProtocolReceivedMessageRouter *messageRouter;
@property (strong, nonatomic) NSMutableDictionary<SDLServiceTypeBox *, SDLProtocolHeader *> *serviceHeaders;
@@ -78,12 +80,11 @@ NS_ASSUME_NONNULL_BEGIN
_prioritizedCollection = [[SDLPrioritizedObjectCollection alloc] init];
_protocolDelegateTable = [NSHashTable weakObjectsHashTable];
_serviceHeaders = [[NSMutableDictionary alloc] init];
+ _receiveProcessor = [[SDLProtocolReceivedMessageProcessor alloc] init];
_messageRouter = [[SDLProtocolReceivedMessageRouter alloc] init];
_messageRouter.delegate = self;
-
_transport = transport;
_transport.delegate = self;
-
_encryptionLifecycleManager = encryptionManager;
return self;
@@ -497,66 +498,22 @@ NS_ASSUME_NONNULL_BEGIN
// Turn received bytes into message objects.
- (void)sdl_handleBytesFromTransport:(NSData *)receivedData {
- // Initialize the receive buffer which will contain bytes while messages are constructed.
- if (self.receiveBuffer == nil) {
- self.receiveBuffer = [NSMutableData dataWithCapacity:(4 * [[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC])];
- }
-
- // Save the data
- [self.receiveBuffer appendData:receivedData];
-
- [self sdl_processMessages];
-}
-
-- (void)sdl_processMessages {
- UInt8 incomingVersion = [SDLProtocolHeader determineVersion:self.receiveBuffer];
-
- // If we have enough bytes, create the header.
- SDLProtocolHeader *header = [SDLProtocolHeader headerForVersion:incomingVersion];
- NSUInteger headerSize = header.size;
- if (self.receiveBuffer.length >= headerSize) {
- [header parse:self.receiveBuffer];
- } else {
- return;
- }
-
- // If we have enough bytes, finish building the message.
- SDLProtocolMessage *message = nil;
- NSUInteger payloadSize = header.bytesInPayload;
- NSUInteger messageSize = headerSize + payloadSize;
- if (self.receiveBuffer.length >= messageSize) {
- NSUInteger payloadOffset = headerSize;
- NSUInteger payloadLength = payloadSize;
- NSData *payload = [self.receiveBuffer subdataWithRange:NSMakeRange(payloadOffset, payloadLength)];
-
+ // Call the state machine, and pass it the bytes to be processed
+ [_receiveProcessor processReceiveBuffer:receivedData withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
// If the message in encrypted and there is payload, try to decrypt it
- if (header.encrypted && payload.length) {
- NSError *decryptError = nil;
+ NSError *decryptError = nil;
+ if (header.encrypted && (payload.length > 0)) {
payload = [self.securityManager decryptData:payload withError:&decryptError];
-
- if (decryptError != nil) {
- SDLLogE(@"Error attempting to decrypt a payload with error: %@", decryptError);
- return;
- }
}
- message = [SDLProtocolMessage messageWithHeader:header andPayload:payload];
- } else {
- // Need to wait for more bytes.
- SDLLogV(@"Protocol header complete, message incomplete, waiting for %ld more bytes. Header: %@", (long)(messageSize - self.receiveBuffer.length), header);
- return;
- }
-
- // Need to maintain the receiveBuffer, remove the bytes from it which we just processed.
- self.receiveBuffer = [[self.receiveBuffer subdataWithRange:NSMakeRange(messageSize, self.receiveBuffer.length - messageSize)] mutableCopy];
-
- // Pass on the message to the message router.
- [self.messageRouter handleReceivedMessage:message protocol:self];
-
- // Call recursively until the buffer is empty or incomplete message is encountered
- if (self.receiveBuffer.length > 0) {
- [self sdl_processMessages];
- }
+ if (decryptError != nil) {
+ return SDLLogE(@"Error attempting to decrypt a payload with error: %@", decryptError);
+ }
+
+ // Build the message and send it on to the router
+ SDLProtocolMessage *message = [SDLProtocolMessage messageWithHeader:header andPayload:payload];
+ [self.messageRouter handleReceivedMessage:message protocol:self];
+ }];
}
diff --git a/SmartDeviceLink/private/SDLProtocolHeader.m b/SmartDeviceLink/private/SDLProtocolHeader.m
index afe93724d..2eb4f16d2 100644
--- a/SmartDeviceLink/private/SDLProtocolHeader.m
+++ b/SmartDeviceLink/private/SDLProtocolHeader.m
@@ -3,8 +3,10 @@
#import "SDLProtocolHeader.h"
+
#import "SDLV1ProtocolHeader.h"
#import "SDLV2ProtocolHeader.h"
+#import "SDLMacros.h"
NS_ASSUME_NONNULL_BEGIN
@@ -41,6 +43,17 @@ NS_ASSUME_NONNULL_BEGIN
return description;
}
+- (NSUInteger)hash {
+ return NSUIntRotateCell(self.version, NSUIntBitCell / 2)
+ ^ NSUIntRotateCell(self.size, NSUIntBitCell / 3)
+ ^ NSUIntRotateCell(self.encrypted, NSUIntBitCell / 4)
+ ^ NSUIntRotateCell(self.frameType, NSUIntBitCell / 5)
+ ^ NSUIntRotateCell(self.serviceType, NSUIntBitCell / 6)
+ ^ NSUIntRotateCell(self.frameData, NSUIntBitCell / 7)
+ ^ NSUIntRotateCell(self.sessionID, NSUIntBitCell / 8)
+ ^ NSUIntRotateCell(self.bytesInPayload, NSUIntBitCell / 9);
+}
+
+ (__kindof SDLProtocolHeader *)headerForVersion:(UInt8)version {
// VERSION DEPENDENT CODE
switch (version) {
diff --git a/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.h b/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.h
new file mode 100644
index 000000000..ac98ecac9
--- /dev/null
+++ b/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.h
@@ -0,0 +1,31 @@
+//
+// SDLProtocolReceivedMessageProcessor.h
+// SmartDeviceLink
+//
+// Created by George Miller on 7/13/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SDLProtocolHeader;
+
+/**
+ * Handles decryption and creation of the message from header and payload. Decryption needed to be handled outside of the MessageProcessor because of access to the securitymanager.
+ * @param header Pointer to the header for the message
+ * @param payload Pointer to the payload of the message
+ */
+typedef void (^StateMachineMessageReadyBlock)(SDLProtocolHeader *header, NSData *payload);
+
+/// Class for processing received byte data into protocol messages
+@interface SDLProtocolReceivedMessageProcessor : NSObject
+
+/**
+ * Processes a data buffer into the state machine.
+ * Loop through the given bytes and call the state machine to process each byte.
+ * @param receiveBuffer The data to process
+ * @param messageReadyBlock Passes back a completed protocol message when one has been assembled
+ */
+- (void)processReceiveBuffer:(NSData *)receiveBuffer withMessageReadyBlock:(StateMachineMessageReadyBlock)messageReadyBlock;
+
+@end
diff --git a/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.m b/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.m
new file mode 100644
index 000000000..10f27a789
--- /dev/null
+++ b/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.m
@@ -0,0 +1,290 @@
+//
+// SDLProtocolProcessMessageByte.m
+// SmartDeviceLink
+//
+// Created by George Miller on 7/13/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+#import "SDLProtocolReceivedMessageProcessor.h"
+
+#import "SDLGlobals.h"
+#import "SDLLogMacros.h"
+#import "SDLProtocolHeader.h"
+
+
+typedef NS_ENUM(NSUInteger, ProcessorState) {
+ START_STATE = 0x0,
+ SERVICE_TYPE_STATE = 0x01,
+ CONTROL_FRAME_INFO_STATE = 0x02,
+ SESSION_ID_STATE = 0x03,
+ DATA_SIZE_1_STATE = 0x04,
+ DATA_SIZE_2_STATE = 0x05,
+ DATA_SIZE_3_STATE = 0x06,
+ DATA_SIZE_4_STATE = 0x07,
+ MESSAGE_1_STATE = 0x08,
+ MESSAGE_2_STATE = 0x09,
+ MESSAGE_3_STATE = 0x0A,
+ MESSAGE_4_STATE = 0x0B,
+ DATA_PUMP_STATE = 0x0C,
+ ERROR_STATE = -1,
+};
+
+@interface SDLProtocolReceivedMessageProcessor ()
+// State management
+@property (assign, nonatomic) ProcessorState state;
+
+// Message assembly state
+@property (strong, nonatomic) SDLProtocolHeader *header;
+@property (strong, nonatomic) NSMutableData *headerBuffer;
+@property (strong, nonatomic) NSMutableData *payloadBuffer;
+
+@property (assign, nonatomic) UInt8 version;
+@property (assign, nonatomic) BOOL encrypted;
+@property (assign, nonatomic) SDLFrameType frameType;
+@property (assign, nonatomic) UInt32 dataLength;
+@property (assign, nonatomic) UInt32 dataBytesRemaining;
+@property (assign, nonatomic) SDLServiceType serviceType;
+@end
+
+@implementation SDLProtocolReceivedMessageProcessor
+
+- (instancetype)init {
+ self = [super init];
+ if (!self) { return nil; }
+
+ [self resetState];
+
+ return self;
+}
+
+- (void)resetState {
+ // Flush Buffers
+ _headerBuffer = [NSMutableData data];
+ _payloadBuffer = [NSMutableData data];
+ _version = 0;
+ _encrypted = NO;
+ _frameType = 0x00;
+ _dataLength = 0;
+ _dataBytesRemaining = 0;
+ _serviceType = 0x00;
+
+ // Reset state
+ _state = START_STATE;
+}
+
+
+- (void)processReceiveBuffer:(NSData *)receiveBuffer withMessageReadyBlock:(StateMachineMessageReadyBlock)messageReadyBlock {
+ const BytePtr bytes = (BytePtr)receiveBuffer.bytes;
+ BOOL messageIsComplete = NO;
+ for (int i = 0; i < receiveBuffer.length; i++) {
+ // If we have reached the end of a message, we need to immediately call the message ready block with the completed data, then reset the buffers and keep pumping data into the state machine
+ messageIsComplete = [self sdl_processByte:(Byte)bytes[i]];
+ if (messageIsComplete) {
+ messageReadyBlock(self.header, [self.payloadBuffer copy]);
+ [self resetState];
+ }
+
+ // If we end up in error state, trigger the reset so we can properly handle the next byte
+ if (_state == ERROR_STATE) {
+ [self resetState];
+ }
+ }
+}
+
+/// This is the state machine. It processes a single byte of a message, checks for errors, and builds up a header buffer and a payload buffer.
+/// When the header and payload are complete, this returns true to notify the calling funciton.
+/// For reference: https://smartdevicelink.com/en/guides/sdl-overview-guides/protocol-spec/
+/// If a byte comes in that does not conform to spec, the buffers are flushed and state is reset.
+/// @param currentByte The byte currently being processed
+/// @return YES if the byte processed is the last byte of a message, else NO
+- (BOOL)sdl_processByte:(Byte)currentByte {
+ BOOL messageHasEnded = NO;
+
+ switch (self.state) {
+ case START_STATE:
+ [self resetState];
+
+ // bits 0-3 for version. (b1111 0000)
+ self.version = (currentByte & 0xF0 ) >> 4;
+
+ // bit 4 for either encryption or compression, depending on version. (b0000 1000)
+ self.encrypted = ((currentByte & 0x08 ) >> 3 == 1);
+
+ // bits 5-7 for frameType. (b0000 0111)
+ self.frameType = (currentByte & 0x07) >> 0;
+
+ self.state = SERVICE_TYPE_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+
+ if ((self.version < 1) || (self.version > 5)) {
+ self.state = ERROR_STATE;
+ SDLLogE(@"Message version is out of spec: %hhu, skipping message", self.version);
+ break;
+ }
+
+ if ((self.frameType < SDLFrameTypeControl) || (self.frameType > SDLFrameTypeConsecutive)) {
+ self.state = ERROR_STATE;
+ SDLLogE(@"Message frameType is out of spec: %hhu, skipping message", self.frameType);
+ }
+ break;
+
+ case SERVICE_TYPE_STATE: {
+ _serviceType = 0x00;
+ // 8 bits for service type
+ _serviceType = currentByte;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+
+ // ServiceType must be one of the defined types, else error.
+ switch (_serviceType) {
+ case SDLServiceTypeControl:
+ case SDLServiceTypeRPC:
+ case SDLServiceTypeAudio:
+ case SDLServiceTypeVideo:
+ case SDLServiceTypeBulkData:
+ self.state = CONTROL_FRAME_INFO_STATE;
+ break;
+ default:
+ self.state = ERROR_STATE;
+ SDLLogE(@"Message serviceType is out of spec: %u, skipping message", _serviceType);
+ break;
+ }
+ }
+ break;
+
+ case CONTROL_FRAME_INFO_STATE: {
+ SDLFrameInfo controlFrameInfo = 0x00;
+ // 8 bits for frame information
+ controlFrameInfo = currentByte;
+ self.state = SESSION_ID_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+
+ // Check for errors. For these two frame types, the frame info should be 0x00
+ if (((self.frameType == SDLFrameTypeFirst) || (self.frameType == SDLFrameTypeSingle)) && (controlFrameInfo != 0x00)){
+ self.state = ERROR_STATE;
+ SDLLogE(@"Message frameType is out of spec. FrameType: %hhu, frameInfo: %hhu, skipping message", self.frameType, controlFrameInfo);
+ }
+ }
+ break;
+
+ case SESSION_ID_STATE:
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ self.state = DATA_SIZE_1_STATE;
+ break;
+
+ // 32 bits for data size
+ case DATA_SIZE_1_STATE:
+ self.dataLength = 0;
+ self.dataLength += (UInt32)(currentByte & 0xFF) << 24;
+ self.state = DATA_SIZE_2_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case DATA_SIZE_2_STATE:
+ self.dataLength += (UInt32)(currentByte & 0xFF) << 16;
+ self.state = DATA_SIZE_3_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case DATA_SIZE_3_STATE:
+ self.dataLength += (UInt32)(currentByte & 0xFF) << 8;
+ self.state = DATA_SIZE_4_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case DATA_SIZE_4_STATE:
+ self.dataLength += (UInt32)(currentByte & 0xFF) << 0;
+ self.state = MESSAGE_1_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+
+ // Set the counter for the data pump.
+ self.dataBytesRemaining = self.dataLength;
+
+ // Version 1 does not have a message ID so we skip to the data pump or the end.
+ if (self.version == 1) {
+ if (self.dataLength == 0) {
+ self.header = [SDLProtocolHeader headerForVersion:self.version];
+ [self.header parse:self.headerBuffer];
+ messageHasEnded = YES;
+ } else {
+ self.state = DATA_PUMP_STATE;
+ }
+ }
+
+ Byte headerSize = 0;
+ if (self.version == 1) {
+ headerSize = 8;
+ } else {
+ headerSize = 12;
+ }
+
+ NSUInteger maxMtuSize = [[SDLGlobals sharedGlobals] mtuSizeForServiceType:_serviceType];
+
+ // Error if the data length is greater than the MTU size for this version
+ if (self.dataLength >= (maxMtuSize - headerSize)) {
+ self.state = ERROR_STATE;
+ SDLLogE(@"Data length %u exceeds maximum MTU size %lu, skipping message", self.dataLength, (unsigned long)maxMtuSize);
+ break;
+ }
+
+ // If this is the first frame, it is not encrypted, and the length is not 8 then error.
+ if ((self.frameType == SDLFrameTypeFirst) && (self.dataLength != 8) && (self.encrypted == NO)) {
+ self.state = ERROR_STATE;
+ SDLLogE(@"Data length may not exceed 8 (%u) for non encrypted first frame, skipping message", self.dataLength);
+ break;
+ }
+ break;
+
+ // 32 bits for data size (version 2+)
+ case MESSAGE_1_STATE:
+ self.state = MESSAGE_2_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case MESSAGE_2_STATE:
+ self.state = MESSAGE_3_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case MESSAGE_3_STATE:
+ self.state = MESSAGE_4_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case MESSAGE_4_STATE:
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ // If there is no payload, we are done.
+ if (self.dataLength == 0) {
+ self.header = [SDLProtocolHeader headerForVersion:self.version];
+ [self.header parse:self.headerBuffer];
+ messageHasEnded = YES;
+ break;
+ }
+
+ self.state = DATA_PUMP_STATE;
+ break;
+
+ case DATA_PUMP_STATE:
+ // The pump state takes bytes in and adds them to the payload array
+ // Note that we do not set state here. If we are pumping, state will not change. If we are done pumping, we return the messageHasEnded and state will be reset externally.
+ [self.payloadBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ self.dataBytesRemaining--;
+
+ // If all the bytes have been read, then parse the header into an object and return the end of message
+ if (self.dataBytesRemaining <= 0) {
+ self.header = [SDLProtocolHeader headerForVersion:self.version];
+ [self.header parse:self.headerBuffer];
+ messageHasEnded = YES;
+ }
+ break;
+
+ case ERROR_STATE:
+ default:
+ [self resetState];
+ break;
+ }
+
+ return messageHasEnded;
+}
+
+@end
diff --git a/SmartDeviceLink/private/SDLV1ProtocolHeader.m b/SmartDeviceLink/private/SDLV1ProtocolHeader.m
index ba6474481..cfbb6b1fc 100644
--- a/SmartDeviceLink/private/SDLV1ProtocolHeader.m
+++ b/SmartDeviceLink/private/SDLV1ProtocolHeader.m
@@ -4,6 +4,8 @@
#import "SDLV1ProtocolHeader.h"
+#import "SDLMacros.h"
+
const int ProtocolV1HeaderByteSize = 8;
NS_ASSUME_NONNULL_BEGIN
@@ -82,6 +84,9 @@ NS_ASSUME_NONNULL_BEGIN
return description;
}
+- (BOOL)isEqual:(SDLV1ProtocolHeader *)object {
+ return (self.hash == object.hash);
+}
@end
diff --git a/SmartDeviceLink/private/SDLV2ProtocolHeader.m b/SmartDeviceLink/private/SDLV2ProtocolHeader.m
index 00567180c..d91e74926 100644
--- a/SmartDeviceLink/private/SDLV2ProtocolHeader.m
+++ b/SmartDeviceLink/private/SDLV2ProtocolHeader.m
@@ -4,6 +4,8 @@
#import "SDLV2ProtocolHeader.h"
+#import "SDLMacros.h"
+
const int ProtocolV2HeaderByteSize = 12;
@@ -123,4 +125,12 @@ const int ProtocolV2HeaderByteSize = 12;
return description;
}
+- (NSUInteger)hash {
+ return [super hash] ^ NSUIntRotateCell(self.messageID, NSUIntBitCell / 10);
+}
+
+- (BOOL)isEqual:(SDLV2ProtocolHeader *)object {
+ return (self.hash == object.hash);
+}
+
@end
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m
index a578ec561..9dac0203f 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m
@@ -15,6 +15,18 @@
QuickSpecBegin(SDLProtocolHeaderSpec)
+__block SDLProtocolHeader *testHeader;
+
+beforeSuite(^ {
+ testHeader = [[SDLProtocolHeader alloc] init];
+ testHeader.encrypted = YES;
+ testHeader.frameType = SDLFrameTypeControl;
+ testHeader.serviceType = SDLServiceTypeRPC;
+ testHeader.frameData = SDLFrameInfoStartService;
+ testHeader.sessionID = 0x53;
+ testHeader.bytesInPayload = 0x1234;
+});
+
describe(@"HeaderForVersion Tests", ^ {
it(@"Should return the correct header", ^ {
expect([SDLProtocolHeader headerForVersion:1]).to(beAKindOf(SDLV1ProtocolHeader.class));
@@ -38,4 +50,30 @@ describe(@"DetermineVersion Tests", ^ {
});
});
+describe(@"hashing tests", ^ {
+ it(@"should return equivalent hash values", ^ {
+ SDLProtocolHeader *equalHeader = [[SDLProtocolHeader alloc] init];
+ equalHeader.encrypted = YES;
+ equalHeader.frameType = SDLFrameTypeControl;
+ equalHeader.serviceType = SDLServiceTypeRPC;
+ equalHeader.frameData = SDLFrameInfoStartService;
+ equalHeader.sessionID = 0x53;
+ equalHeader.bytesInPayload = 0x1234;
+
+ expect([testHeader hash]).to(equal([equalHeader hash]));
+ });
+
+ it(@"should return unequivalent hash values", ^ {
+ SDLProtocolHeader *unequalHeader = [[SDLProtocolHeader alloc] init];
+ unequalHeader.encrypted = NO;
+ unequalHeader.frameType = SDLFrameTypeFirst;
+ unequalHeader.serviceType = SDLServiceTypeVideo;
+ unequalHeader.frameData = SDLFrameInfoStartService;
+ unequalHeader.sessionID = 0x54;
+ unequalHeader.bytesInPayload = 0x1234;
+
+ expect([testHeader hash]).toNot(equal([unequalHeader hash]));
+ });
+});
+
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m
index a7c6c1660..f2218f7db 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m
@@ -86,4 +86,21 @@ describe(@"RPCPayloadWithData Test", ^ {
});
});
+describe(@"equality tests", ^ {
+ it (@"should be equal to copy of header", ^ {
+ // Create exact copy of test header
+ SDLV1ProtocolHeader *equalHeader = [testHeader copy];
+
+ expect([testHeader isEqual:equalHeader]).to(beTrue());
+ });
+
+ it (@"should not be equal to a different header", ^ {
+ // create a slighty different version of test header
+ SDLV1ProtocolHeader *unequalHeader = [testHeader copy];
+ unequalHeader.encrypted = NO;
+
+ expect(([testHeader isEqual:unequalHeader])).to(beFalse());
+ });
+});
+
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m
index 1ade6e426..ec06d2b4d 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m
@@ -90,4 +90,21 @@ describe(@"RPCPayloadWithData Test", ^ {
});
});
+describe(@"equality tests", ^ {
+ it (@"should be equal to copy of header", ^ {
+ // Create exact copy of test header
+ SDLV2ProtocolHeader *equalHeader = [testHeader copy];
+
+ expect([testHeader isEqual:equalHeader]).to(beTrue());
+ });
+
+ it (@"should not be equal to a different header", ^ {
+ // Create a slighty different version of test header
+ SDLV2ProtocolHeader *unequalHeader = [testHeader copy];
+ unequalHeader.messageID = 0x6DAB424E;
+
+ expect(([testHeader isEqual:unequalHeader])).to(beFalse());
+ });
+});
+
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolReceivedMessageProcessorSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolReceivedMessageProcessorSpec.m
new file mode 100644
index 000000000..7065e89a8
--- /dev/null
+++ b/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolReceivedMessageProcessorSpec.m
@@ -0,0 +1,769 @@
+//
+// SDLProtocolReceivedMessageProcessorSpec.m
+// SmartDeviceLinkTests
+//
+// Created by George Miller on 8/9/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLProtocol.h"
+#import "SDLProtocolReceivedMessageProcessor.h"
+#import "SDLV2ProtocolHeader.h"
+#import "SDLV2ProtocolMessage.h"
+
+typedef NS_ENUM(NSInteger, ProcessorState) {
+ START_STATE = 0x0,
+ SERVICE_TYPE_STATE = 0x01,
+ CONTROL_FRAME_INFO_STATE = 0x02,
+ SESSION_ID_STATE = 0x03,
+ DATA_SIZE_1_STATE = 0x04,
+ DATA_SIZE_2_STATE = 0x05,
+ DATA_SIZE_3_STATE = 0x06,
+ DATA_SIZE_4_STATE = 0x07,
+ MESSAGE_1_STATE = 0x08,
+ MESSAGE_2_STATE = 0x09,
+ MESSAGE_3_STATE = 0x0A,
+ MESSAGE_4_STATE = 0x0B,
+ DATA_PUMP_STATE = 0x0C,
+ ERROR_STATE = -1,
+};
+
+@interface SDLProtocolReceivedMessageProcessor ()
+// State management
+@property (assign, nonatomic) ProcessorState state;
+
+// Message assembly state
+@property (strong, nonatomic) SDLProtocolHeader *header;
+@property (strong, nonatomic) NSMutableData *headerBuffer;
+@property (strong, nonatomic) NSMutableData *payloadBuffer;
+
+@property (assign, nonatomic) UInt8 version;
+@property (assign, nonatomic) BOOL encrypted;
+@property (assign, nonatomic) SDLFrameType frameType;
+@property (assign, nonatomic) UInt32 dataLength;
+@property (assign, nonatomic) UInt32 dataBytesRemaining;
+@property (assign, nonatomic) SDLServiceType serviceType;
+@end
+
+QuickSpecBegin(SDLProtocolReceivedMessageProcessorSpec)
+
+describe(@"The received message processor", ^{
+ __block SDLProtocolReceivedMessageProcessor *testProcessor = nil;
+ __block NSMutableData *testBuffer;
+ __block NSMutableData *testHeaderBuffer;
+ __block SDLProtocolHeader *messageReadyHeader = nil;
+ __block SDLProtocolHeader *expectedMessageReadyHeader = nil;
+ __block NSData *messageReadyPayload = nil;
+ __block NSData *expectedPayloadBuffer = nil;
+
+ beforeEach(^{
+ testProcessor = [[SDLProtocolReceivedMessageProcessor alloc] init];
+ testBuffer = [NSMutableData data];
+ testHeaderBuffer = [NSMutableData data];
+ messageReadyHeader = nil;
+ expectedMessageReadyHeader = nil;
+ messageReadyPayload = nil;
+ });
+
+ it(@"test processor should be initialized correctly", ^{
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.version)).to(equal(0));
+ expect(@(testProcessor.encrypted)).to(equal(NO));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeControl));
+ expect(@(testProcessor.dataLength)).to(equal(0));
+ expect(@(testProcessor.dataBytesRemaining)).to(equal(0));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeControl));
+ });
+
+ describe(@"when in START_STATE", ^{
+ it(@"should transition to next state when receiving version 1", ^{
+ Byte testByte = 0x11;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(1));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving version 2", ^{
+ Byte testByte = 0x21;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(2));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving version 3", ^{
+ Byte testByte = 0x31;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(3));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving version 4", ^{
+ Byte testByte = 0x41;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(4));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving version 5", ^{
+ Byte testByte = 0x51;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(5));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving a byte with a frameType of SDLFrameTypeControl", ^{
+ Byte testByte = 0x10;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving a byte with a frameType of SDLFrameTypeSingle", ^{
+ Byte testByte = 0x11;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeSingle));
+ });
+
+ it(@"should transition to next state when receiving a byte with a frameType of SDLFrameTypeFirst", ^{
+ Byte testByte = 0x12;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeFirst));
+ });
+
+ it(@"should transition to next state when receiving a byte with a frameType of SDLFrameTypeConsecutive", ^{
+ Byte testByte = 0x13;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeConsecutive));
+ });
+
+ it(@"should reset state when receiving a byte with a bad version 0", ^{
+ Byte testByte = 0x01;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(testProcessor.version).to(equal(0));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+
+ it(@"should reset state when receiving a byte with a bad version 6", ^{
+ Byte testByte = 0x61;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(testProcessor.version).to(equal(0));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+
+ it(@"should reset state when receiving a byte with an invalid frameType of 6", ^{
+ Byte testByte = 0x46; //0100 0 110
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.frameType)).to(equal(0));
+ expect(testProcessor.version).to(equal(0));
+ });
+ });
+
+ // transitions to CONTROL_FRAME_INFO_STATE when in SERVICE_TYPE_STATE
+ describe(@"when in SERVICE_TYPE_STATE", ^{
+
+ beforeEach(^{
+ testProcessor.state = SERVICE_TYPE_STATE;
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeControl byte", ^{
+ Byte testByte = SDLServiceTypeControl;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeControl));
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeRPC byte", ^{
+ Byte testByte = SDLServiceTypeRPC;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeRPC));
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeAudio byte", ^{
+ Byte testByte = SDLServiceTypeAudio;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeAudio));
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeVideo byte", ^{
+ Byte testByte = SDLServiceTypeVideo;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeVideo));
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeBulkData byte", ^{
+ Byte testByte = SDLServiceTypeBulkData;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeBulkData));
+ });
+
+ it(@"should reset state when receiving an invalid byte", ^{
+ Byte testByte = 0xFF;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeControl));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+ });
+
+ describe(@"when in CONTROL_FRAME_INFO_STATE", ^{
+
+ beforeEach(^{
+ testProcessor.state = CONTROL_FRAME_INFO_STATE;
+ });
+
+ it(@"should transition to next state when receiving a valid byte", ^{
+ Byte testByte = 0x00;
+ testProcessor.frameType = SDLFrameTypeFirst;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SESSION_ID_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeFirst));
+ });
+
+ it(@"should reset state when receiving a byte where controlFrameInfo is not 0 and frameType is SDLFrameTypeFirst", ^{
+ Byte testByte = 0x01;
+ testProcessor.frameType = SDLFrameTypeFirst;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeControl));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+
+ it(@"should resets state when receiving a byte where controlFrameInfo is not 0 and frameType is SDLFrameTypeSingle", ^{
+ Byte testByte = 0x01;
+ testProcessor.frameType = SDLFrameTypeSingle;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeControl));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+ });
+
+ it(@"should transition to DATA_SIZE_1_STATE when in SESSION_ID_STATE and receiving a byte", ^{
+ testProcessor.state = SESSION_ID_STATE;
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_SIZE_1_STATE));
+ expect(@(testProcessor.dataLength)).to(equal(0));
+ });
+
+ it(@"should transition to DATA_SIZE_2_STATE when in DATA_SIZE_1_STATE and receiving a byte", ^{
+ testProcessor.state = DATA_SIZE_1_STATE;
+ Byte testByte = 0x02;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_SIZE_2_STATE));
+ expect(@(testProcessor.dataLength)).to(equal((UInt32)(testByte & 0xFF) << 24));
+ });
+
+ it(@"should transitions to DATA_SIZE_3_STATE when in DATA_SIZE_2_STATE and receiving a byte", ^{
+ testProcessor.state = DATA_SIZE_2_STATE;
+ Byte testByte = 0x02;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_SIZE_3_STATE));
+ expect(@(testProcessor.dataLength)).to(equal((UInt32)(testByte & 0xFF) << 16));
+ });
+
+ it(@"should transition to DATA_SIZE_4_STATE when in DATA_SIZE_3_STATE and receiving a byte", ^{
+ testProcessor.state = DATA_SIZE_3_STATE;
+ Byte testByte = 0x02;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_SIZE_4_STATE));
+ expect(@(testProcessor.dataLength)).to(equal((UInt32)(testByte & 0xFF) << 8));
+ });
+
+ describe(@"when in DATA_SIZE_4_STATE and the version is 1", ^{
+ beforeEach(^{
+ testProcessor.state = DATA_SIZE_4_STATE;
+ testProcessor.version = 1;
+
+ messageReadyHeader = nil;
+ messageReadyPayload = nil;
+
+ //need a valid headerbuffer.
+ Byte firstByte = ((testProcessor.version & 0x0f) << 4) + (0 << 3) + (1 & 0x07); //version 2 with no encryption, frametype 1
+ UInt32 dataLength = 3;
+ const Byte testBytes[8] = {firstByte, 0x00, 0x00, 0x00, (dataLength >> 24) & 0xff, (dataLength >> 16) & 0xff, (dataLength >> 8) & 0xff, (dataLength) & 0xff };
+ [testHeaderBuffer appendBytes:&testBytes length:8];
+ UInt32 messageID = 0;
+ Byte messageIDBytes[4] = {(messageID >> 24) & 0xff, (messageID >> 16) & 0xff, (messageID >> 8) & 0xff, (messageID) & 0xff};
+ [testHeaderBuffer appendBytes:&messageIDBytes length:4];
+
+ testProcessor.headerBuffer = testHeaderBuffer;
+ expectedMessageReadyHeader= [SDLProtocolHeader headerForVersion:testProcessor.version];
+ [expectedMessageReadyHeader parse:testHeaderBuffer];
+
+ expectedPayloadBuffer = [NSData data];
+ });
+
+ it(@"should reset state when receiving a byte and determines the data length is 0", ^{
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+ messageReadyHeader = [SDLProtocolHeader headerForVersion:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(equal(expectedMessageReadyHeader));
+ expect(messageReadyPayload).to(equal(expectedPayloadBuffer));
+ }];
+ expect(@(testProcessor.dataLength)).to(equal(0));
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.version)).to(equal(0));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+
+ it(@"should transition to DATA_PUMP_STATE when receiving a byte and determines the data length is greater than 0", ^{
+ Byte testByte = 0x01;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_PUMP_STATE));
+ expect(@(testProcessor.dataLength)).to(equal(1));
+ expect(@(testProcessor.version)).to(equal(1));
+ });
+
+ it(@"should transition to START_STATE when receiving a byte and determines the data length is greater than maxMtuSize", ^{
+ testProcessor.serviceType = SDLServiceTypeControl;
+ testProcessor.dataLength = 200000;
+
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(0));
+ expect(@(testProcessor.dataLength)).to(equal(0));
+ expect(@(testProcessor.version)).to(equal(0));
+ });
+ });
+
+ describe(@"when in DATA_SIZE_4_STATE and the version is greater than 1", ^{
+ beforeEach(^{
+ testProcessor.state = DATA_SIZE_4_STATE;
+ messageReadyHeader = nil;
+ messageReadyPayload = nil;
+
+ testProcessor.version = 1;
+ //need a valid headerbuffer.
+ Byte firstByte = ((testProcessor.version & 0x0f) << 4) + (0 << 3) + (1 & 0x07); //version 2 with no encryption, frametype 1
+ UInt32 dataLength = 3;
+ const Byte testBytes[8] = {firstByte, 0x00, 0x00, 0x00, (dataLength >> 24) & 0xff, (dataLength >> 16) & 0xff, (dataLength >> 8) & 0xff, (dataLength) & 0xff };
+ [testHeaderBuffer appendBytes:&testBytes length:8];
+ UInt32 messageID = 0;
+ Byte messageIDBytes[4] = {(messageID >> 24) & 0xff, (messageID >> 16) & 0xff, (messageID >> 8) & 0xff, (messageID) & 0xff};
+ [testHeaderBuffer appendBytes:&messageIDBytes length:4];
+
+ testProcessor.headerBuffer = testHeaderBuffer;
+ expectedMessageReadyHeader= [SDLProtocolHeader headerForVersion:testProcessor.version];
+ [expectedMessageReadyHeader parse:testHeaderBuffer];
+
+ expectedPayloadBuffer = [NSData data];
+ });
+
+ it(@"should transition to MESSAGE_1_STATE when it receives a byte", ^{
+ testProcessor.version = 2;
+ testProcessor.dataLength = 0;
+
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(MESSAGE_1_STATE));
+ expect(testProcessor.version).to(equal(2));
+ });
+ });
+
+ it(@"should transition to MESSAGE_2_STATE when in MESSAGE_1_STATE, it receives a byte", ^{
+ testProcessor.state = MESSAGE_1_STATE;
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(MESSAGE_2_STATE));
+ });
+
+ it(@"should transition to MESSAGE_3_STATE when in MESSAGE_2_STATE and it receives a byte", ^{
+ testProcessor.state = MESSAGE_2_STATE;
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(MESSAGE_3_STATE));
+ });
+
+ it(@"should transition to MESSAGE_4_STATE when in MESSAGE_3_STATE and it receives a byte", ^{
+ testProcessor.state = MESSAGE_3_STATE;
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(MESSAGE_4_STATE));
+ });
+
+ describe(@"when in MESSAGE_4_STATE and version is greater than 1", ^{
+ beforeEach(^{
+ testProcessor.state = MESSAGE_4_STATE;
+ testProcessor.version = 2;
+ //need a valid headerbuffer.
+ Byte firstByte = ((testProcessor.version & 0x0f) << 4) + (0 << 3) + (1 & 0x07); //version 2 with no encryption, frametype 1
+ UInt32 dataLength = 3;
+ const Byte testBytes[8] = {firstByte, 0x00, 0x00, 0x00, (dataLength >> 24) & 0xff, (dataLength >> 16) & 0xff, (dataLength >> 8) & 0xff, (dataLength) & 0xff };
+ [testHeaderBuffer appendBytes:&testBytes length:8];
+ UInt32 messageID = 0;
+ Byte messageIDBytes[4] = {(messageID >> 24) & 0xff, (messageID >> 16) & 0xff, (messageID >> 8) & 0xff, (messageID) & 0xff};
+ [testHeaderBuffer appendBytes:&messageIDBytes length:4];
+
+ testProcessor.headerBuffer = testHeaderBuffer;
+ expectedMessageReadyHeader= [SDLProtocolHeader headerForVersion:testProcessor.version];
+ [expectedMessageReadyHeader parse:testHeaderBuffer];
+
+ expectedPayloadBuffer = [NSData data];
+
+ messageReadyHeader = nil;
+ messageReadyPayload = nil;
+
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+ });
+
+ it(@"should reset state when data length is 0 and receiving a byte", ^{
+ testProcessor.dataLength = 0;
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(equal(expectedMessageReadyHeader));
+ expect(messageReadyPayload).to(equal(expectedPayloadBuffer));
+ }];
+ expect(testProcessor.version).to(equal(0));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ });
+
+ it(@"should transition to DATA_PUMP_STATE when datalength is greater than 0 and receiving a byte", ^{
+ testProcessor.dataLength = 1;
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_PUMP_STATE));
+ expect(testProcessor.version).to(equal(2));
+ });
+ });
+
+ describe(@"in DATA_PUMP_STATE", ^{
+ beforeEach(^{
+ testProcessor.state = DATA_PUMP_STATE;
+ testProcessor.version = 3;
+ //need a valid header buffer.
+ Byte firstByte = ((testProcessor.version & 0x0f) << 4) + (0 << 3) + (1 & 0x07); //version 2 with no encryption, frametype 1
+ UInt32 dataLength = 3;
+ const Byte testBytes[8] = {firstByte, 0x00, 0x00, 0x00, (dataLength >> 24) & 0xff, (dataLength >> 16) & 0xff, (dataLength >> 8) & 0xff, (dataLength) & 0xff };
+ [testHeaderBuffer appendBytes:&testBytes length:8];
+ UInt32 messageID = 0;
+ Byte messageIDBytes[4] = {(messageID >> 24) & 0xff, (messageID >> 16) & 0xff, (messageID >> 8) & 0xff, (messageID) & 0xff};
+ [testHeaderBuffer appendBytes:&messageIDBytes length:4];
+
+ testProcessor.headerBuffer = testHeaderBuffer;
+ expectedMessageReadyHeader= [SDLProtocolHeader headerForVersion:testProcessor.version];
+ [expectedMessageReadyHeader parse:testHeaderBuffer];
+
+ Byte testByte = 0xBA;
+ [testBuffer appendBytes:&testByte length:1];
+ });
+
+ it(@"should stay in current state when dataBytesRemaining is greater than 1 and receiving a byte", ^{
+ testProcessor.dataBytesRemaining = 2;
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_PUMP_STATE));
+ expect(testProcessor.dataBytesRemaining).to(equal(1));
+ expect(testProcessor.version).to(equal(3));
+ });
+
+ it(@"should transition to START_STATE when dataBytesRemaining is 1 and receiving a byte", ^{
+ testProcessor.dataBytesRemaining = 1;
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(equal(expectedMessageReadyHeader));
+ expect(messageReadyPayload).to(equal(testBuffer));
+ }];
+ expect(testProcessor.dataBytesRemaining).to(equal(0));
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(testProcessor.version).to(equal(0));
+ });
+ });
+});
+
+
+QuickSpecEnd