summaryrefslogtreecommitdiff
path: root/SmartDeviceLink/private
diff options
context:
space:
mode:
Diffstat (limited to 'SmartDeviceLink/private')
-rw-r--r--SmartDeviceLink/private/SDLIAPDataSession.m6
-rw-r--r--SmartDeviceLink/private/SDLMenuManager.m10
-rw-r--r--SmartDeviceLink/private/SDLPresentAlertOperation.m18
-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/SDLTextAndGraphicManager.m24
-rw-r--r--SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h6
-rw-r--r--SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m27
-rw-r--r--SmartDeviceLink/private/SDLV1ProtocolHeader.m5
-rw-r--r--SmartDeviceLink/private/SDLV2ProtocolHeader.m10
12 files changed, 441 insertions, 72 deletions
diff --git a/SmartDeviceLink/private/SDLIAPDataSession.m b/SmartDeviceLink/private/SDLIAPDataSession.m
index 06d2e1f1d..be8b8cfbb 100644
--- a/SmartDeviceLink/private/SDLIAPDataSession.m
+++ b/SmartDeviceLink/private/SDLIAPDataSession.m
@@ -58,10 +58,14 @@ NS_ASSUME_NONNULL_BEGIN
if (bytesWritten >= 0) {
if (bytesWritten == bytesRemaining) {
[self.sendDataQueue popBuffer];
- } else {
+ } else if (bytesRemaining > bytesWritten) {
// Cleave the sent bytes from the data, the remainder will sit at the head of the queue
SDLLogV(@"SDLIAPDataSession writeDataToSessionStream bytes written %ld", (long)bytesWritten);
[remainder replaceBytesInRange:NSMakeRange(0, (NSUInteger)bytesWritten) withBytes:NULL length:0];
+ } else {
+ // Error processing current data. Remove corrupted buffer
+ SDLLogE(@"Unable to remove sent bytes. Bytes remaining is less than bytes written %lu < %lu. Clearing buffer", bytesRemaining, bytesWritten);
+ [self.sendDataQueue popBuffer];
}
} else {
// The write operation failed but there is no further information about the error. This can occur when disconnecting from an external accessory.
diff --git a/SmartDeviceLink/private/SDLMenuManager.m b/SmartDeviceLink/private/SDLMenuManager.m
index ae422345f..264bf94c1 100644
--- a/SmartDeviceLink/private/SDLMenuManager.m
+++ b/SmartDeviceLink/private/SDLMenuManager.m
@@ -227,6 +227,16 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
+ // Check if a passed cell is a "re-created" cell without a cellID. If it is, then try to find the equivalent cell and use it instead
+ if (cell != nil && cell.cellId == UINT32_MAX) {
+ for (SDLMenuCell *headUnitCell in self.menuCells) {
+ if ([cell isEqual:headUnitCell]) {
+ cell = headUnitCell;
+ break;
+ }
+ }
+ }
+
// Create the operation
SDLMenuShowOperation *showMenuOp = [[SDLMenuShowOperation alloc] initWithConnectionManager:self.connectionManager toMenuCell:cell completionHandler:^(NSError * _Nullable error) {
if (error != nil) {
diff --git a/SmartDeviceLink/private/SDLPresentAlertOperation.m b/SmartDeviceLink/private/SDLPresentAlertOperation.m
index 9359106f5..28ecec877 100644
--- a/SmartDeviceLink/private/SDLPresentAlertOperation.m
+++ b/SmartDeviceLink/private/SDLPresentAlertOperation.m
@@ -66,6 +66,7 @@ static const int SDLAlertSoftButtonCount = 4;
@property (assign, nonatomic) UInt16 cancelId;
@property (copy, nonatomic, nullable) NSError *internalError;
@property (assign, atomic) BOOL isAlertPresented;
+@property (assign, nonatomic) BOOL alertIconUploaded;
@end
@@ -90,6 +91,7 @@ static const int SDLAlertSoftButtonCount = 4;
_cancelId = cancelID;
_operationId = [NSUUID UUID];
_currentWindowCapability = currentWindowCapability;
+ _alertIconUploaded = NO;
return self;
}
@@ -193,8 +195,14 @@ static const int SDLAlertSoftButtonCount = 4;
/// @param handler Called when all images have been uploaded.
- (void)sdl_uploadImagesWithCompletionHandler:(void (^)(void))handler {
NSMutableArray<SDLArtwork *> *artworksToBeUploaded = [NSMutableArray array];
- if ([self sdl_supportsAlertIcon] && [self.fileManager fileNeedsUpload:self.alertView.icon]) {
- [artworksToBeUploaded addObject:self.alertView.icon];
+ if ([self sdl_supportsAlertIcon] && (self.alertView.icon != nil)) {
+ if ([self.fileManager fileNeedsUpload:self.alertView.icon]) {
+ // If the file is not uploaded, attempt to upload it
+ [artworksToBeUploaded addObject:self.alertView.icon];
+ } else if ([self.fileManager hasUploadedFile:self.alertView.icon] || self.alertView.icon.isStaticIcon) {
+ // If the file is already uploaded, add it to the uploaded set so we can show it
+ self.alertIconUploaded = YES;
+ }
}
// Don't upload artworks for buttons that will not be shown.
@@ -219,12 +227,16 @@ static const int SDLAlertSoftButtonCount = 4;
return YES;
} completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ __strong typeof(weakself) strongself = weakself;
if (error != nil) {
SDLLogE(@"Error uploading alert images: %@", error);
} else {
SDLLogD(@"All alert images uploaded");
}
+ if ([artworkNames containsObject:strongself.alertView.icon.name]) {
+ strongself.alertIconUploaded = YES;
+ }
return handler();
}];
}
@@ -293,7 +305,7 @@ static const int SDLAlertSoftButtonCount = 4;
SDLAlert *alert = [[SDLAlert alloc] init];
[self sdl_assembleAlertText:alert];
alert.duration = @((NSUInteger)(self.alertView.timeout * 1000));
- alert.alertIcon = ([self sdl_supportsAlertIcon] && ![self.fileManager fileNeedsUpload:self.alertView.icon]) ? self.alertView.icon.imageRPC : nil;
+ alert.alertIcon = self.alertIconUploaded ? self.alertView.icon.imageRPC : nil;
alert.progressIndicator = @(self.alertView.showWaitIndicator);
alert.cancelID = @(self.cancelId);
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/SDLTextAndGraphicManager.m b/SmartDeviceLink/private/SDLTextAndGraphicManager.m
index 34c4dcf54..23a7a3df9 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicManager.m
+++ b/SmartDeviceLink/private/SDLTextAndGraphicManager.m
@@ -157,7 +157,7 @@ NS_ASSUME_NONNULL_BEGIN
}
} else if (self.isDirty) {
self.isDirty = NO;
- [self sdl_updateAndCancelPreviousOperations:YES completionHandler:handler];
+ [self sdl_createOperationWithCompletionHandler:handler];
} else {
if (handler != nil) {
handler([NSError sdl_textAndGraphicManager_nothingToUpdate]);
@@ -165,12 +165,8 @@ NS_ASSUME_NONNULL_BEGIN
}
}
-- (void)sdl_updateAndCancelPreviousOperations:(BOOL)supersedePreviousOperations completionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler {
+- (void)sdl_createOperationWithCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler {
SDLLogD(@"Updating text and graphics");
- if (self.transactionQueue.operationCount > 0 && supersedePreviousOperations) {
- SDLLogV(@"Transactions already exist, cancelling them");
- [self.transactionQueue cancelAllOperations];
- }
__weak typeof(self) weakSelf = self;
SDLTextAndGraphicUpdateOperation *updateOperation = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager currentCapabilities:self.windowCapability currentScreenData:self.currentScreenData newState:[self currentState] currentScreenDataUpdatedHandler:^(SDLTextAndGraphicState *_Nullable newScreenData, NSError *_Nullable error) {
@@ -182,7 +178,10 @@ NS_ASSUME_NONNULL_BEGIN
} else if (error != nil) {
// Invalidate data that's different from our current screen data if a Show or SetDisplayLayout fails. This will prevent subsequent `Show`s from failing if the request failed due to the developer setting invalid data or subsequent `SetDisplayLayout`s from failing if the template is not supported on the module.
[strongSelf sdl_resetFieldsToCurrentScreenData];
- [strongSelf sdl_updatePendingOperationsWithNewScreenData:strongSelf.currentScreenData];
+ SDLTextAndGraphicState *errorState = error.userInfo[SDLTextAndGraphicFailedScreenStateErrorKey];
+ if (errorState != nil) {
+ [strongSelf sdl_updatePendingOperationsWithFailedScreenState:errorState];
+ }
}
} updateCompletionHandler:handler];
@@ -204,6 +203,15 @@ NS_ASSUME_NONNULL_BEGIN
}
}
+- (void)sdl_updatePendingOperationsWithFailedScreenState:(SDLTextAndGraphicState *)errorState {
+ for (NSOperation *operation in self.transactionQueue.operations) {
+ if (operation.isExecuting) { continue; }
+ SDLTextAndGraphicUpdateOperation *updateOp = (SDLTextAndGraphicUpdateOperation *)operation;
+
+ [updateOp updateTargetStateWithErrorState:errorState];
+ }
+}
+
- (void)sdl_resetFieldsToCurrentScreenData {
_textField1 = _currentScreenData.textField1;
_textField2 = _currentScreenData.textField2;
@@ -394,7 +402,7 @@ NS_ASSUME_NONNULL_BEGIN
// Auto-send an updated show
if ([self sdl_hasData]) {
// TODO: HAX: Capability updates cannot supersede earlier updates because of the case where a developer batched a `changeLayout` call w/ T&G changes on <6.0 systems could cause this to come in before the operation completes. That would cause the operation to report a "failure" (because it was superseded by this call) when in fact the operation didn't fail at all and is just being adjusted. Even though iOS is able to tell the developer that it was superseded, Java Suite cannot, and therefore we are matching functionality with their library.
- [self sdl_updateAndCancelPreviousOperations:NO completionHandler:nil];
+ [self sdl_createOperationWithCompletionHandler:nil];
}
}
diff --git a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h
index d3f88b667..e06306937 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h
+++ b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h
@@ -21,6 +21,8 @@
NS_ASSUME_NONNULL_BEGIN
+extern NSString *const SDLTextAndGraphicFailedScreenStateErrorKey;
+
typedef void(^SDLTextAndGraphicUpdateCompletionHandler)(NSError *__nullable error);
typedef void(^CurrentDataUpdatedHandler)(SDLTextAndGraphicState *__nullable newScreenData, NSError *__nullable error);
@@ -38,6 +40,10 @@ typedef void(^CurrentDataUpdatedHandler)(SDLTextAndGraphicState *__nullable newS
/// @param updateCompletionHandler The handler potentially passed by the developer to be called when the update finishes
- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager currentCapabilities:(SDLWindowCapability *)currentCapabilities currentScreenData:(SDLTextAndGraphicState *)currentData newState:(SDLTextAndGraphicState *)newState currentScreenDataUpdatedHandler:(CurrentDataUpdatedHandler)currentDataUpdatedHandler updateCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)updateCompletionHandler;
+/// Changes updated state to remove failed updates from previous update operations. This will find and revert those failed updates back to current screen data so that we don't duplicate the failure.
+/// @param errorState A updated state that failed in a previous operation that will be used to filter out erroneous data
+- (void)updateTargetStateWithErrorState:(SDLTextAndGraphicState *)errorState;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
index cd47531e2..246e1a014 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
+++ b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
@@ -26,6 +26,8 @@
NS_ASSUME_NONNULL_BEGIN
+NSString *const SDLTextAndGraphicFailedScreenStateErrorKey = @"failedScreenState";
+
@interface SDLTextAndGraphicUpdateOperation ()
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@@ -97,6 +99,23 @@ NS_ASSUME_NONNULL_BEGIN
}
}
+- (void)updateTargetStateWithErrorState:(SDLTextAndGraphicState *)errorState {
+ self.updatedState.textField1 = [errorState.textField1 isEqualToString:self.updatedState.textField1] ? self.currentScreenData.textField1 : self.updatedState.textField1;
+ self.updatedState.textField2 = [errorState.textField2 isEqualToString:self.updatedState.textField2] ? self.currentScreenData.textField2 : self.updatedState.textField2;
+ self.updatedState.textField3 = [errorState.textField3 isEqualToString:self.updatedState.textField3] ? self.currentScreenData.textField3 : self.updatedState.textField3;
+ self.updatedState.textField4 = [errorState.textField4 isEqualToString:self.updatedState.textField4] ? self.currentScreenData.textField4 : self.updatedState.textField4;
+ self.updatedState.mediaTrackTextField = [errorState.mediaTrackTextField isEqualToString:self.updatedState.mediaTrackTextField] ? self.currentScreenData.mediaTrackTextField : self.updatedState.mediaTrackTextField;
+ self.updatedState.title = [errorState.title isEqualToString:self.updatedState.title] ? self.currentScreenData.title : self.updatedState.title;
+ self.updatedState.primaryGraphic = [errorState.primaryGraphic isEqual:self.updatedState.primaryGraphic] ? self.currentScreenData.primaryGraphic : self.updatedState.primaryGraphic;
+ self.updatedState.secondaryGraphic = [errorState.secondaryGraphic isEqual:self.updatedState.secondaryGraphic] ? self.currentScreenData.secondaryGraphic : self.updatedState.secondaryGraphic;
+ self.updatedState.alignment = [errorState.alignment isEqualToEnum:self.updatedState.alignment] ? self.currentScreenData.alignment : self.updatedState.alignment;
+ self.updatedState.textField1Type = [errorState.textField1Type isEqualToEnum:self.updatedState.textField1Type] ? self.currentScreenData.textField1Type : self.updatedState.textField1Type;
+ self.updatedState.textField2Type = [errorState.textField2Type isEqualToEnum:self.updatedState.textField2Type] ? self.currentScreenData.textField2Type : self.updatedState.textField2Type;
+ self.updatedState.textField3Type = [errorState.textField3Type isEqualToEnum:self.updatedState.textField3Type] ? self.currentScreenData.textField3Type : self.updatedState.textField3Type;
+ self.updatedState.textField4Type = [errorState.textField4Type isEqualToEnum:self.updatedState.textField4Type] ? self.currentScreenData.textField4Type : self.updatedState.textField4Type;
+ self.updatedState.templateConfig = [errorState.templateConfig isEqual:self.updatedState.templateConfig] ? self.currentScreenData.templateConfig : self.updatedState.templateConfig;
+}
+
#pragma mark - Send Show / Set Display Layout
- (void)sdl_updateGraphicsAndShow:(SDLShow *)show {
@@ -158,8 +177,12 @@ NS_ASSUME_NONNULL_BEGIN
SDLLogD(@"Text and Graphic Show completed successfully");
[strongSelf sdl_updateCurrentScreenDataFromShow:request];
} else {
- SDLLogD(@"Text and Graphic Show failed");
- self.currentDataUpdatedHandler(nil, error);
+ SDLLogE(@"Text and Graphic Show failed: %@", error);
+ NSError *updateError = [NSError errorWithDomain:error.domain code:error.code userInfo:@{
+ NSUnderlyingErrorKey: error.userInfo,
+ SDLTextAndGraphicFailedScreenStateErrorKey: self.updatedState
+ }];
+ self.currentDataUpdatedHandler(nil, updateError);
}
handler(error);
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