From dce65375886fb346e7cc21b642d05bdf4d2bf246 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Wed, 17 Sep 2025 12:46:16 -0400 Subject: [PATCH 1/2] ref: Envelope deserialization to Swift --- Sources/Sentry/PrivateSentrySDKOnly.m | 2 +- .../Sentry/Public/SentryEnvelopeItemHeader.h | 2 +- Sources/Sentry/SentryAttachment.m | 2 +- Sources/Sentry/SentryFileManager.m | 2 +- Sources/Sentry/SentryHttpTransport.m | 2 +- Sources/Sentry/SentryMigrateSessionInit.m | 2 +- Sources/Sentry/SentrySerialization.m | 159 ------------------ .../Sentry/include/SentryAttachment+Private.h | 2 +- Sources/Sentry/include/SentrySerialization.h | 2 - .../Swift/Helper/DataDeserialization.swift | 152 +++++++++++++++++ .../Helper/SentryFileManagerTests.swift | 12 +- .../Helper/SentryMigrateSessionInitTests.m | 3 +- .../Helper/SentrySerializationNilTests.m | 3 +- .../Helper/SentrySerializationTests.swift | 60 +++---- .../SentryCrashIntegrationTests.swift | 4 +- .../Networking/SentryHttpTransportTests.swift | 12 +- Tests/SentryTests/SentrySDKTests.swift | 2 +- 17 files changed, 208 insertions(+), 215 deletions(-) diff --git a/Sources/Sentry/PrivateSentrySDKOnly.m b/Sources/Sentry/PrivateSentrySDKOnly.m index 012ff3f554b..d69588494a8 100644 --- a/Sources/Sentry/PrivateSentrySDKOnly.m +++ b/Sources/Sentry/PrivateSentrySDKOnly.m @@ -50,7 +50,7 @@ + (void)captureEnvelope:(SentryEnvelope *)envelope + (nullable SentryEnvelope *)envelopeWithData:(NSData *)data { - return [SentrySerialization envelopeWithData:data]; + return [DataDeserialization envelopeWithData:data]; } #if !SDK_V9 diff --git a/Sources/Sentry/Public/SentryEnvelopeItemHeader.h b/Sources/Sentry/Public/SentryEnvelopeItemHeader.h index 89800fb5ef4..df005de67e9 100644 --- a/Sources/Sentry/Public/SentryEnvelopeItemHeader.h +++ b/Sources/Sentry/Public/SentryEnvelopeItemHeader.h @@ -31,7 +31,7 @@ SENTRY_NO_INIT - (instancetype)initWithType:(NSString *)type length:(NSUInteger)length - contentType:(NSString *)contentType + contentType:(NSString *_Nullable)contentType itemCount:(NSNumber *)itemCount; /** diff --git a/Sources/Sentry/SentryAttachment.m b/Sources/Sentry/SentryAttachment.m index 457f5f2fd28..46e7767a816 100644 --- a/Sources/Sentry/SentryAttachment.m +++ b/Sources/Sentry/SentryAttachment.m @@ -88,7 +88,7 @@ - (instancetype)initWithPath:(NSString *)path } SentryAttachmentType -typeForSentryAttachmentName(NSString *name) +typeForSentryAttachmentName(NSString *_Nullable name) { if ([name isEqualToString:kSentryAttachmentTypeNameViewHierarchy]) { return kSentryAttachmentTypeViewHierarchy; diff --git a/Sources/Sentry/SentryFileManager.m b/Sources/Sentry/SentryFileManager.m index adba119f61a..e4acbed3f51 100644 --- a/Sources/Sentry/SentryFileManager.m +++ b/Sources/Sentry/SentryFileManager.m @@ -1073,7 +1073,7 @@ - (void)handleEnvelopesLimit [envelopePathsCopy removeObjectAtIndex:i]; NSData *envelopeData = [[NSFileManager defaultManager] contentsAtPath:envelopeFilePath]; - SentryEnvelope *envelope = [SentrySerialization envelopeWithData:envelopeData]; + SentryEnvelope *envelope = [DataDeserialization envelopeWithData:envelopeData]; BOOL didMigrateSessionInit = [SentryMigrateSessionInit migrateSessionInit:envelope diff --git a/Sources/Sentry/SentryHttpTransport.m b/Sources/Sentry/SentryHttpTransport.m index 2600eb47821..cb51e932dc3 100644 --- a/Sources/Sentry/SentryHttpTransport.m +++ b/Sources/Sentry/SentryHttpTransport.m @@ -339,7 +339,7 @@ - (void)sendAllCachedEnvelopes envelopeFilePath = envelopeFileContents.path; - envelope = [SentrySerialization envelopeWithData:envelopeFileContents.contents]; + envelope = [DataDeserialization envelopeWithData:envelopeFileContents.contents]; if (nil == envelope) { SENTRY_LOG_DEBUG(@"Envelope contained no deserializable data."); [self deleteEnvelopeAndSendNext:envelopeFilePath]; diff --git a/Sources/Sentry/SentryMigrateSessionInit.m b/Sources/Sentry/SentryMigrateSessionInit.m index e17b0b0986b..d2356edd877 100644 --- a/Sources/Sentry/SentryMigrateSessionInit.m +++ b/Sources/Sentry/SentryMigrateSessionInit.m @@ -56,7 +56,7 @@ + (BOOL)setInitFlagOnNextEnvelopeWithSameSessionId:(SentrySession *)session continue; } - SentryEnvelope *envelope = [SentrySerialization envelopeWithData:envelopeData]; + SentryEnvelope *envelope = [DataDeserialization envelopeWithData:envelopeData]; if (nil != envelope) { BOOL didSetInitFlag = [self setInitFlagIfContainsSameSessionId:session.sessionId diff --git a/Sources/Sentry/SentrySerialization.m b/Sources/Sentry/SentrySerialization.m index 0505e38f9be..dbbf4808bed 100644 --- a/Sources/Sentry/SentrySerialization.m +++ b/Sources/Sentry/SentrySerialization.m @@ -76,165 +76,6 @@ + (NSData *_Nullable)dataWithEnvelope:(SentryEnvelope *)envelope return envelopeData; } -+ (SentryEnvelope *_Nullable)envelopeWithData:(NSData *)data -{ - SentryEnvelopeHeader *envelopeHeader = nil; - const unsigned char *bytes = [data bytes]; - NSUInteger envelopeHeaderIndex = 0; - - for (NSUInteger i = 0; i < data.length; ++i) { - if (bytes[i] == '\n') { - envelopeHeaderIndex = i; - // Envelope header end - NSData *headerData = [NSData dataWithBytes:bytes length:i]; -#ifdef DEBUG - NSString *headerString = [[NSString alloc] initWithData:headerData - encoding:NSUTF8StringEncoding]; - SENTRY_LOG_DEBUG(@"Header %@", headerString); -#endif - NSError *error = nil; - NSDictionary *headerDictionary = [NSJSONSerialization JSONObjectWithData:headerData - options:0 - error:&error]; - if (nil != error) { - SENTRY_LOG_ERROR(@"Failed to parse envelope header %@", error); - break; - } - - SentryId *eventId = nil; - NSString *eventIdAsString = headerDictionary[@"event_id"]; - if (nil != eventIdAsString) { - eventId = [[SentryId alloc] initWithUUIDString:eventIdAsString]; - } - - SentrySdkInfo *sdkInfo = nil; - if (nil != headerDictionary[@"sdk"] && - [headerDictionary[@"sdk"] isKindOfClass:[NSDictionary class]]) { - sdkInfo = [[SentrySdkInfo alloc] - initWithDict:SENTRY_UNWRAP_NULLABLE(NSDictionary, headerDictionary[@"sdk"])]; - } - - SentryTraceContext *traceContext = nil; - if (nil != headerDictionary[@"trace"] && - [headerDictionary[@"trace"] isKindOfClass:[NSDictionary class]]) { - traceContext = [[SentryTraceContext alloc] - initWithDict:SENTRY_UNWRAP_NULLABLE(NSDictionary, headerDictionary[@"trace"])]; - } - - envelopeHeader = [[SentryEnvelopeHeader alloc] initWithId:eventId - sdkInfo:sdkInfo - traceContext:traceContext]; - - if (headerDictionary[@"sent_at"] != nil && - [headerDictionary[@"sent_at"] isKindOfClass:[NSString class]]) { - envelopeHeader.sentAt = sentry_fromIso8601String( - SENTRY_UNWRAP_NULLABLE(NSString, headerDictionary[@"sent_at"])); - } - - break; - } - } - - if (nil == envelopeHeader) { - SENTRY_LOG_ERROR(@"Invalid envelope. No header found."); - return nil; - } - - if (envelopeHeaderIndex == 0) { - SENTRY_LOG_ERROR(@"EnvelopeHeader was parsed, its index is expected."); - return nil; - } - - // Parse items - NSUInteger itemHeaderStart = envelopeHeaderIndex + 1; - - NSMutableArray *items = [NSMutableArray new]; - NSUInteger endOfEnvelope = data.length - 1; - - for (NSUInteger i = itemHeaderStart; i <= endOfEnvelope; ++i) { - if (bytes[i] == '\n' || i == endOfEnvelope) { - - NSData *itemHeaderData = - [data subdataWithRange:NSMakeRange(itemHeaderStart, i - itemHeaderStart)]; -#ifdef DEBUG - NSString *itemHeaderString = [[NSString alloc] initWithData:itemHeaderData - encoding:NSUTF8StringEncoding]; - SENTRY_LOG_DEBUG(@"Item Header %@", itemHeaderString); -#endif - NSError *error = nil; - NSDictionary *headerDictionary = [NSJSONSerialization JSONObjectWithData:itemHeaderData - options:0 - error:&error]; - if (nil != error) { - SENTRY_LOG_ERROR(@"Failed to parse envelope item header %@", error); - return nil; - } - NSString *_Nullable nullableType = [headerDictionary valueForKey:@"type"]; - if (nil == nullableType) { - SENTRY_LOG_ERROR(@"Envelope item type is required."); - break; - } - NSString *_Nonnull type = SENTRY_UNWRAP_NULLABLE(NSString, nullableType); - - NSNumber *bodyLengthNumber = [headerDictionary valueForKey:@"length"]; - NSUInteger bodyLength = [bodyLengthNumber unsignedIntegerValue]; - if (endOfEnvelope == i && bodyLength != 0) { - SENTRY_LOG_ERROR( - @"Envelope item has no data but header indicates it's length is %d.", - (int)bodyLength); - break; - } - - NSString *filename = [headerDictionary valueForKey:@"filename"]; - NSString *contentType = [headerDictionary valueForKey:@"content_type"]; - NSString *attachmentType = [headerDictionary valueForKey:@"attachment_type"]; - NSNumber *itemCount = [headerDictionary valueForKey:@"item_count"]; - - SentryEnvelopeItemHeader *itemHeader; - if (nil != filename) { - itemHeader = [[SentryEnvelopeAttachmentHeader alloc] - initWithType:type - length:bodyLength - filename:filename - contentType:contentType - attachmentType:typeForSentryAttachmentName(attachmentType)]; - } else if (nil != itemCount) { - itemHeader = [[SentryEnvelopeItemHeader alloc] initWithType:type - length:bodyLength - contentType:contentType - itemCount:itemCount]; - } else { - itemHeader = [[SentryEnvelopeItemHeader alloc] initWithType:type length:bodyLength]; - } - - if (endOfEnvelope == i) { - i++; // 0 byte attachment - } - - if (bodyLength > 0 && data.length < (i + 1 + bodyLength)) { - SENTRY_LOG_ERROR(@"Envelope is corrupted or has invalid data. Trying to read %li " - @"bytes by skipping %li from a buffer of %li bytes.", - (unsigned long)data.length, (unsigned long)bodyLength, (long)(i + 1)); - return nil; - } - - NSData *itemBody = [data subdataWithRange:NSMakeRange(i + 1, bodyLength)]; - SentryEnvelopeItem *envelopeItem = [[SentryEnvelopeItem alloc] initWithHeader:itemHeader - data:itemBody]; - [items addObject:envelopeItem]; - i = itemHeaderStart = i + 1 + [bodyLengthNumber integerValue]; - } - } - - if (items.count == 0) { - SENTRY_LOG_ERROR(@"Envelope has no items."); - return nil; - } - - SentryEnvelope *envelope = [[SentryEnvelope alloc] initWithHeader:envelopeHeader items:items]; - return envelope; -} - + (NSData *_Nullable)dataWithSession:(SentrySession *)session { return [self dataWithJSONObject:[session serialize]]; diff --git a/Sources/Sentry/include/SentryAttachment+Private.h b/Sources/Sentry/include/SentryAttachment+Private.h index b1baa9f60ec..32c012e9324 100644 --- a/Sources/Sentry/include/SentryAttachment+Private.h +++ b/Sources/Sentry/include/SentryAttachment+Private.h @@ -16,7 +16,7 @@ typedef NS_ENUM(NSInteger, SentryAttachmentType) { NSString *nameForSentryAttachmentType(SentryAttachmentType attachmentType); -SentryAttachmentType typeForSentryAttachmentName(NSString *name); +SentryAttachmentType typeForSentryAttachmentName(NSString *_Nullable name); @interface SentryAttachment () SENTRY_NO_INIT diff --git a/Sources/Sentry/include/SentrySerialization.h b/Sources/Sentry/include/SentrySerialization.h index 5870fc08eff..7231dcf9334 100644 --- a/Sources/Sentry/include/SentrySerialization.h +++ b/Sources/Sentry/include/SentrySerialization.h @@ -15,8 +15,6 @@ NS_ASSUME_NONNULL_BEGIN + (NSData *_Nullable)dataWithEnvelope:(SentryEnvelope *)envelope; -+ (SentryEnvelope *_Nullable)envelopeWithData:(NSData *)data; - /** * Retrieves the json object from an event envelope item data. */ diff --git a/Sources/Swift/Helper/DataDeserialization.swift b/Sources/Swift/Helper/DataDeserialization.swift index 482cb01f0f3..2b6b3bc6474 100644 --- a/Sources/Swift/Helper/DataDeserialization.swift +++ b/Sources/Swift/Helper/DataDeserialization.swift @@ -1,3 +1,5 @@ +@_implementationOnly import _SentryPrivate + @_spi(Private) @objc public final class DataDeserialization: NSObject { @objc(sessionWithData:) public static func session(with data: Data) -> SentrySession? { do { @@ -29,4 +31,154 @@ return nil } } + + //swiftlint:disable cyclomatic_complexity function_body_length + @objc(envelopeWithData:) public static func envelope(with data: Data) -> SentryEnvelope? { + var envelopeHeader: SentryEnvelopeHeader? + let bytes = [UInt8](data) + var envelopeHeaderIndex: Int = 0 + + for i in 0.. 0 && data.count < (i + 1 + Int(bodyLength)) { + SentrySDKLog.error( + "Envelope is corrupted or has invalid data. Trying to read \(bodyLength) bytes by skipping \(i + 1) from a buffer of \(data.count) bytes." + ) + return nil + } + + let itemBody = (data as NSData).subdata(with: NSRange(location: i + 1, length: Int(bodyLength))) + let envelopeItem = SentryEnvelopeItem(header: itemHeader, data: itemBody) + items.append(envelopeItem) + + i = i + 1 + Int(bodyLength) + itemHeaderStart = i + } catch { + SentrySDKLog.error("Failed to parse envelope item header \(error)") + return nil + } + } + } + + if items.isEmpty { + SentrySDKLog.error("Envelope has no items.") + return nil + } + + return SentryEnvelope(header: envelopeHeaderUnwrapped, items: items) + } + //swiftlint:enable cyclomatic_complexity function_body_length } diff --git a/Tests/SentryTests/Helper/SentryFileManagerTests.swift b/Tests/SentryTests/Helper/SentryFileManagerTests.swift index 60fe525fef5..43bbf050c23 100644 --- a/Tests/SentryTests/Helper/SentryFileManagerTests.swift +++ b/Tests/SentryTests/Helper/SentryFileManagerTests.swift @@ -511,7 +511,7 @@ class SentryFileManagerTests: XCTestCase { XCTAssertEqual(fixture.maxCacheItems, envelopes.count) for i in 0.. /** @@ -13,7 +14,7 @@ @implementation SentryMigrateSessionInitTests - (void)testWithGarbageParametersDoesNotCrash { - SentryEnvelope *envelope = [SentrySerialization envelopeWithData:[[NSData alloc] init]]; + SentryEnvelope *envelope = [DataDeserialization envelopeWithData:[[NSData alloc] init]]; [SentryMigrateSessionInit migrateSessionInit:envelope envelopesDirPath:@"asdf" envelopeFilePaths:@[]]; diff --git a/Tests/SentryTests/Helper/SentrySerializationNilTests.m b/Tests/SentryTests/Helper/SentrySerializationNilTests.m index ea00d4257db..965bbc300d1 100644 --- a/Tests/SentryTests/Helper/SentrySerializationNilTests.m +++ b/Tests/SentryTests/Helper/SentrySerializationNilTests.m @@ -1,4 +1,5 @@ #import "SentrySerialization.h" +#import "SentrySwift.h" #import @interface SentrySerializationNilTests : XCTestCase @@ -15,7 +16,7 @@ - (void)testSentryEnvelopeSerializerWithNilInput { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wnonnull" - XCTAssertNil([SentrySerialization envelopeWithData:nil]); + XCTAssertNil([DataDeserialization envelopeWithData:nil]); #pragma clang diagnostic pop } diff --git a/Tests/SentryTests/Helper/SentrySerializationTests.swift b/Tests/SentryTests/Helper/SentrySerializationTests.swift index 7b391207e0a..a60002104fa 100644 --- a/Tests/SentryTests/Helper/SentrySerializationTests.swift +++ b/Tests/SentryTests/Helper/SentrySerializationTests.swift @@ -78,7 +78,7 @@ class SentrySerializationTests: XCTestCase { XCTAssertEqual(1, envelope.items.count) XCTAssertEqual("event", try XCTUnwrap(envelope.items.first).header.type) - let deserializedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: serializeEnvelope(envelope: envelope))) + let deserializedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: serializeEnvelope(envelope: envelope))) XCTAssertEqual(envelope.header.eventId, deserializedEnvelope.header.eventId) assertDefaultSdkInfoSet(deserializedEnvelope: deserializedEnvelope) XCTAssertEqual(1, deserializedEnvelope.items.count) @@ -114,7 +114,7 @@ class SentrySerializationTests: XCTestCase { XCTAssertNil(envelope.header.eventId) XCTAssertEqual(itemsCount, Int(envelope.items.count)) - let deserializedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: serializeEnvelope(envelope: envelope))) + let deserializedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: serializeEnvelope(envelope: envelope))) XCTAssertNil(deserializedEnvelope.header.eventId) XCTAssertEqual(itemsCount, deserializedEnvelope.items.count) assertDefaultSdkInfoSet(deserializedEnvelope: deserializedEnvelope) @@ -142,7 +142,7 @@ class SentrySerializationTests: XCTestCase { XCTAssertEqual("attachment", try XCTUnwrap(envelope.items.first).header.type) XCTAssertEqual(0, Int(try XCTUnwrap(envelope.items.first).header.length)) - let deserializedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: serializeEnvelope(envelope: envelope))) + let deserializedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: serializeEnvelope(envelope: envelope))) XCTAssertEqual(1, deserializedEnvelope.items.count) XCTAssertEqual("attachment", try XCTUnwrap(deserializedEnvelope.items.first).header.type) XCTAssertEqual(0, try XCTUnwrap(deserializedEnvelope.items.first).header.length) @@ -155,7 +155,7 @@ class SentrySerializationTests: XCTestCase { let envelopeHeader = SentryEnvelopeHeader(id: nil, sdkInfo: sdkInfo, traceContext: nil) let envelope = SentryEnvelope(header: envelopeHeader, singleItem: createItemWithEmptyAttachment()) - let deserializedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: serializeEnvelope(envelope: envelope))) + let deserializedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: serializeEnvelope(envelope: envelope))) XCTAssertEqual(sdkInfo, deserializedEnvelope.header.sdkInfo) } @@ -164,7 +164,7 @@ class SentrySerializationTests: XCTestCase { let envelopeHeader = SentryEnvelopeHeader(id: nil, traceContext: Fixture.traceContext) let envelope = SentryEnvelope(header: envelopeHeader, singleItem: createItemWithEmptyAttachment()) - let deserializedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: serializeEnvelope(envelope: envelope))) + let deserializedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: serializeEnvelope(envelope: envelope))) XCTAssertNotNil(deserializedEnvelope.header.traceContext) let traceContext = try XCTUnwrap(deserializedEnvelope.header.traceContext) @@ -178,7 +178,7 @@ class SentrySerializationTests: XCTestCase { let envelopeHeader = SentryEnvelopeHeader(id: nil, traceContext: trace) let envelope = SentryEnvelope(header: envelopeHeader, singleItem: createItemWithEmptyAttachment()) - let deserializedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: serializeEnvelope(envelope: envelope))) + let deserializedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: serializeEnvelope(envelope: envelope))) XCTAssertNotNil(deserializedEnvelope.header.traceContext) let traceContext = try XCTUnwrap(deserializedEnvelope.header.traceContext) @@ -205,7 +205,7 @@ class SentrySerializationTests: XCTestCase { let envelopeHeader = SentryEnvelopeHeader(id: nil, traceContext: trace) let envelope = SentryEnvelope(header: envelopeHeader, singleItem: createItemWithEmptyAttachment()) - let deserializedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: serializeEnvelope(envelope: envelope))) + let deserializedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: serializeEnvelope(envelope: envelope))) // -- Assert -- XCTAssertNotNil(deserializedEnvelope.header.traceContext) @@ -217,13 +217,13 @@ class SentrySerializationTests: XCTestCase { let envelopeHeader = SentryEnvelopeHeader(id: nil, sdkInfo: nil, traceContext: nil) let envelope = SentryEnvelope(header: envelopeHeader, singleItem: createItemWithEmptyAttachment()) - let deserializedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: serializeEnvelope(envelope: envelope))) + let deserializedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: serializeEnvelope(envelope: envelope))) XCTAssertNil(deserializedEnvelope.header.sdkInfo) } func testEnvelopeWithData_ZeroByteItem_ReturnsEnvelope() throws { let itemData = Data("{}\n{\"length\":0,\"type\":\"attachment\"}\n".utf8) - XCTAssertNotNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNotNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_EnvelopeWithHeaderAndItemWithAttachment() throws { @@ -236,7 +236,7 @@ class SentrySerializationTests: XCTestCase { \(payloadAsString) """.utf8) - let envelope = try XCTUnwrap(SentrySerialization.envelope(with: itemData), "Failed to deserialize envelope") + let envelope = try XCTUnwrap(DataDeserialization.envelope(with: itemData), "Failed to deserialize envelope") XCTAssertEqual(eventId, try XCTUnwrap(envelope.header.eventId)) XCTAssertEqual(1, envelope.items.count) @@ -257,7 +257,7 @@ class SentrySerializationTests: XCTestCase { helloworld """.utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_ItemHeaderDefinesLengthButAttachmentIsEmpty_ReturnsNil() throws { @@ -269,7 +269,7 @@ class SentrySerializationTests: XCTestCase { """.utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_AttachmentFollowedByEmptyAttachment() throws { @@ -284,7 +284,7 @@ class SentrySerializationTests: XCTestCase { """.utf8) - let envelope = try XCTUnwrap(SentrySerialization.envelope(with: itemData)) + let envelope = try XCTUnwrap(DataDeserialization.envelope(with: itemData)) XCTAssertEqual(eventId, try XCTUnwrap(envelope.header.eventId)) XCTAssertEqual(2, envelope.items.count) @@ -312,7 +312,7 @@ class SentrySerializationTests: XCTestCase { \(payloadAsString) """.utf8) - let envelope = try XCTUnwrap(SentrySerialization.envelope(with: itemData)) + let envelope = try XCTUnwrap(DataDeserialization.envelope(with: itemData)) XCTAssertEqual(eventId, try XCTUnwrap(envelope.header.eventId)) XCTAssertEqual(2, envelope.items.count) @@ -336,7 +336,7 @@ class SentrySerializationTests: XCTestCase { itemData.appendString("{\"event_id\":\"\(eventId)\"}\n") itemData.appendString("{\"length\":0,\"type\":\"attachment\"}\n") - let envelope = try XCTUnwrap(SentrySerialization.envelope(with: itemData)) + let envelope = try XCTUnwrap(DataDeserialization.envelope(with: itemData)) XCTAssertEqual(eventId, try XCTUnwrap(envelope.header.eventId)) XCTAssertEqual(1, envelope.items.count) @@ -357,7 +357,7 @@ class SentrySerializationTests: XCTestCase { \(payloadAsString) """.utf8) - let envelope = try XCTUnwrap(SentrySerialization.envelope(with: itemData), "Failed to deserialize envelope") + let envelope = try XCTUnwrap(DataDeserialization.envelope(with: itemData), "Failed to deserialize envelope") XCTAssertEqual(eventId, try XCTUnwrap(envelope.header.eventId)) XCTAssertEqual(1, envelope.items.count) @@ -392,7 +392,7 @@ class SentrySerializationTests: XCTestCase { itemData.appendString("{\"length\":\(logs.count),\"type\":\"log\",\"item_count\":1,\"content_type\":\"application/vnd.sentry.items.log+json\"}\n") itemData.append(logs) - let envelope = try XCTUnwrap(SentrySerialization.envelope(with: itemData), "Failed to deserialize envelope") + let envelope = try XCTUnwrap(DataDeserialization.envelope(with: itemData), "Failed to deserialize envelope") XCTAssertEqual(1, envelope.items.count) let item = try XCTUnwrap(envelope.items.first) @@ -406,7 +406,7 @@ class SentrySerializationTests: XCTestCase { } func testEnvelopeWithData_EmptyEnvelope_ReturnsNil() throws { - XCTAssertNil(SentrySerialization.envelope(with: Data())) + XCTAssertNil(DataDeserialization.envelope(with: Data())) } func testEnvelopeWithData_CorruptHeader_ReturnsNil() throws { @@ -414,13 +414,13 @@ class SentrySerializationTests: XCTestCase { itemData.append(contentsOf: [0xFF, 0xFF, 0xFF]) // Invalid UTF-8 bytes itemData.append(Data("\n".utf8)) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_EmptyHeader_ReturnsNil() throws { let itemData = Data("\n".utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_EmptyItemHeader_ReturnsNil() throws { @@ -431,7 +431,7 @@ class SentrySerializationTests: XCTestCase { """.utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_EmptyItemHeaderFollowedByNewLine_ReturnsNil() throws { @@ -443,7 +443,7 @@ class SentrySerializationTests: XCTestCase { """.utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_ItemHeaderWithSpace_ReturnsNil() throws { @@ -454,7 +454,7 @@ class SentrySerializationTests: XCTestCase { """.utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_ItemHeaderWithoutType_ReturnsNil() throws { @@ -467,7 +467,7 @@ class SentrySerializationTests: XCTestCase { \(payloadAsString) """.utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_ItemHeaderWithoutNewLine_ReturnsNil() throws { @@ -477,7 +477,7 @@ class SentrySerializationTests: XCTestCase { itemData.appendString("{\"event_id\":\"\(eventId)\"}\n") itemData.appendString("{\"length\":0,\"type\":\"attachment\"}") - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_CorruptItemHeader() throws { @@ -486,22 +486,22 @@ class SentrySerializationTests: XCTestCase { itemData.append(contentsOf: [0xFF]) // Invalid UTF-8 byte itemData.appendString("\n") - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_ItemWithoutType_ReturnsNil() { let itemData = Data("{}\n{\"length\":0}".utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_WithoutItem_ReturnsNil() { let itemData = Data("{}\n".utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testEnvelopeWithData_WithoutLineBreak_ReturnsNil() { let itemData = Data("{}".utf8) - XCTAssertNil(SentrySerialization.envelope(with: itemData)) + XCTAssertNil(DataDeserialization.envelope(with: itemData)) } func testSerializeSession() throws { @@ -636,7 +636,7 @@ class SentrySerializationTests: XCTestCase { let corruptedData = data[0.. 3 { let unzippedBody = try XCTUnwrap(sentry_unzippedData(XCTUnwrap(sessionRequest.httpBody))) let requestUnzippedBody = try XCTUnwrap(sentry_unzippedData(XCTUnwrap(XCTUnwrap(fixture.requestManager.requests.invocations.element(at: 3)).httpBody))) - let actualEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: unzippedBody)) - let expectedEnvelope = try XCTUnwrap(SentrySerialization.envelope(with: requestUnzippedBody)) + let actualEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: unzippedBody)) + let expectedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: requestUnzippedBody)) try EnvelopeUtils.assertEnvelope(expected: expectedEnvelope, actual: actualEnvelope) } else { XCTFail("Expected a fourth invocation") diff --git a/Tests/SentryTests/SentrySDKTests.swift b/Tests/SentryTests/SentrySDKTests.swift index 37039cb76d6..635ed57aba1 100644 --- a/Tests/SentryTests/SentrySDKTests.swift +++ b/Tests/SentryTests/SentrySDKTests.swift @@ -426,7 +426,7 @@ class SentrySDKTests: XCTestCase { // Assert let eventEnvelopeItems = try fileManager.getAllEnvelopes().map { fileContent in - return try XCTUnwrap(SentrySerialization.envelope(with: fileContent.contents)) + return try XCTUnwrap(DataDeserialization.envelope(with: fileContent.contents)) }.flatMap { envelope in return envelope.items.filter { $0.header.type == SentryEnvelopeItemTypes.event } } From e147e1812fc835a8eda168153147e493ba60d468 Mon Sep 17 00:00:00 2001 From: Noah Martin Date: Thu, 18 Sep 2025 09:38:51 -0400 Subject: [PATCH 2/2] PR feedback --- Sentry.xcodeproj/project.pbxproj | 8 +- Sources/Sentry/PrivateSentrySDKOnly.m | 2 +- Sources/Sentry/SentryFileManager.m | 6 +- Sources/Sentry/SentryHttpTransport.m | 2 +- Sources/Sentry/SentryMigrateSessionInit.m | 6 +- ...n.swift => SentrySerializationSwift.swift} | 7 +- .../Helper/SentryFileManagerTests.swift | 16 ++-- .../Helper/SentryMigrateSessionInitTests.m | 2 +- .../Helper/SentrySerializationNilTests.m | 2 +- .../Helper/SentrySerializationTests.swift | 76 +++++++++---------- .../SentryCrashIntegrationTests.swift | 4 +- .../Networking/SentryHttpTransportTests.swift | 12 +-- Tests/SentryTests/SentryHubTests.swift | 2 +- Tests/SentryTests/SentrySDKTests.swift | 2 +- 14 files changed, 74 insertions(+), 73 deletions(-) rename Sources/Swift/Helper/{DataDeserialization.swift => SentrySerializationSwift.swift} (97%) diff --git a/Sentry.xcodeproj/project.pbxproj b/Sentry.xcodeproj/project.pbxproj index bd9c97ed0f1..968ab721ab9 100644 --- a/Sentry.xcodeproj/project.pbxproj +++ b/Sentry.xcodeproj/project.pbxproj @@ -1122,7 +1122,7 @@ FAE80C242E4695B40010A595 /* SentryEvent+Serialize.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE80C232E4695AE0010A595 /* SentryEvent+Serialize.h */; }; FAEC270E2DF3526000878871 /* SentryUserFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAEC270D2DF3526000878871 /* SentryUserFeedback.swift */; }; FAEC273D2DF3933A00878871 /* NSData+Unzip.m in Sources */ = {isa = PBXBuildFile; fileRef = FAEC273C2DF3933200878871 /* NSData+Unzip.m */; }; - FAEEC0522E75E55F00E79CA9 /* DataDeserialization.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAEEC04C2E75E55A00E79CA9 /* DataDeserialization.swift */; }; + FAEEC0522E75E55F00E79CA9 /* SentrySerializationSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAEEC04C2E75E55A00E79CA9 /* SentrySerializationSwift.swift */; }; FAEFA12F2E4FAE1900C431D9 /* SentrySDKSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = FAEFA1292E4FAE1700C431D9 /* SentrySDKSettings.swift */; }; FAF120182E70C08F006E1DA3 /* SentryEnvelopeHeaderHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = FAF120122E70C088006E1DA3 /* SentryEnvelopeHeaderHelper.h */; }; FAF1201A2E70C0EE006E1DA3 /* SentryEnvelopeHeaderHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = FAF120192E70C0EA006E1DA3 /* SentryEnvelopeHeaderHelper.m */; }; @@ -2470,7 +2470,7 @@ FAEC270D2DF3526000878871 /* SentryUserFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryUserFeedback.swift; sourceTree = ""; }; FAEC273C2DF3933200878871 /* NSData+Unzip.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSData+Unzip.m"; sourceTree = ""; }; FAEC273E2DF393E000878871 /* NSData+Unzip.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSData+Unzip.h"; sourceTree = ""; }; - FAEEC04C2E75E55A00E79CA9 /* DataDeserialization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataDeserialization.swift; sourceTree = ""; }; + FAEEC04C2E75E55A00E79CA9 /* SentrySerializationSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySerializationSwift.swift; sourceTree = ""; }; FAEFA1292E4FAE1700C431D9 /* SentrySDKSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentrySDKSettings.swift; sourceTree = ""; }; FAF120122E70C088006E1DA3 /* SentryEnvelopeHeaderHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryEnvelopeHeaderHelper.h; path = include/SentryEnvelopeHeaderHelper.h; sourceTree = ""; }; FAF120192E70C0EA006E1DA3 /* SentryEnvelopeHeaderHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryEnvelopeHeaderHelper.m; sourceTree = ""; }; @@ -2606,7 +2606,7 @@ isa = PBXGroup; children = ( 62CB19242E77F8FD00AF5DA2 /* SentryDispatchSourceWrapper.swift */, - FAEEC04C2E75E55A00E79CA9 /* DataDeserialization.swift */, + FAEEC04C2E75E55A00E79CA9 /* SentrySerializationSwift.swift */, FA94E71B2E6F26BF00576666 /* SentrySerialization+ReplayRecording.swift */, FA94E7232E6F32FA00576666 /* SentryEnvelopeItemType.swift */, FA458CBD2E691A6E0061B13D /* SentryProcessInfo.swift */, @@ -5583,7 +5583,7 @@ FA90FAA82E06614E008CAAE8 /* SentryExtraPackages.swift in Sources */, 7BD86ED1264A7CF6005439DB /* SentryAppStartMeasurement.m in Sources */, 7DC27EC723997EB7006998B5 /* SentryAutoBreadcrumbTrackingIntegration.m in Sources */, - FAEEC0522E75E55F00E79CA9 /* DataDeserialization.swift in Sources */, + FAEEC0522E75E55F00E79CA9 /* SentrySerializationSwift.swift in Sources */, D84D2CDF2C2BF9370011AF8A /* SentryReplayType.swift in Sources */, 63FE717B20DA4C1100CDBAE8 /* SentryCrashReport.c in Sources */, D8F67B222BEAB6CC00C9197B /* SentryRRWebEvent.swift in Sources */, diff --git a/Sources/Sentry/PrivateSentrySDKOnly.m b/Sources/Sentry/PrivateSentrySDKOnly.m index d69588494a8..d82a14874af 100644 --- a/Sources/Sentry/PrivateSentrySDKOnly.m +++ b/Sources/Sentry/PrivateSentrySDKOnly.m @@ -50,7 +50,7 @@ + (void)captureEnvelope:(SentryEnvelope *)envelope + (nullable SentryEnvelope *)envelopeWithData:(NSData *)data { - return [DataDeserialization envelopeWithData:data]; + return [SentrySerializationSwift envelopeWithData:data]; } #if !SDK_V9 diff --git a/Sources/Sentry/SentryFileManager.m b/Sources/Sentry/SentryFileManager.m index e4acbed3f51..21b415f8905 100644 --- a/Sources/Sentry/SentryFileManager.m +++ b/Sources/Sentry/SentryFileManager.m @@ -974,7 +974,7 @@ - (nullable SentrySession *)readSession:(NSString *)sessionFilePath return nil; } } - SentrySession *currentSession = [DataDeserialization sessionWithData:currentData]; + SentrySession *currentSession = [SentrySerializationSwift sessionWithData:currentData]; if (nil == currentSession) { SENTRY_LOG_ERROR( @"Data stored in session: '%@' was not parsed as session.", sessionFilePath); @@ -993,7 +993,7 @@ - (SentryAppState *_Nullable)readAppStateFrom:(NSString *)path SENTRY_LOG_WARN(@"No app state data found at %@", path); return nil; } - return [DataDeserialization appStateWithData:currentData]; + return [SentrySerializationSwift appStateWithData:currentData]; } - (void)deleteAppStateFrom:(NSString *)path @@ -1073,7 +1073,7 @@ - (void)handleEnvelopesLimit [envelopePathsCopy removeObjectAtIndex:i]; NSData *envelopeData = [[NSFileManager defaultManager] contentsAtPath:envelopeFilePath]; - SentryEnvelope *envelope = [DataDeserialization envelopeWithData:envelopeData]; + SentryEnvelope *envelope = [SentrySerializationSwift envelopeWithData:envelopeData]; BOOL didMigrateSessionInit = [SentryMigrateSessionInit migrateSessionInit:envelope diff --git a/Sources/Sentry/SentryHttpTransport.m b/Sources/Sentry/SentryHttpTransport.m index cb51e932dc3..488ec9c9456 100644 --- a/Sources/Sentry/SentryHttpTransport.m +++ b/Sources/Sentry/SentryHttpTransport.m @@ -339,7 +339,7 @@ - (void)sendAllCachedEnvelopes envelopeFilePath = envelopeFileContents.path; - envelope = [DataDeserialization envelopeWithData:envelopeFileContents.contents]; + envelope = [SentrySerializationSwift envelopeWithData:envelopeFileContents.contents]; if (nil == envelope) { SENTRY_LOG_DEBUG(@"Envelope contained no deserializable data."); [self deleteEnvelopeAndSendNext:envelopeFilePath]; diff --git a/Sources/Sentry/SentryMigrateSessionInit.m b/Sources/Sentry/SentryMigrateSessionInit.m index d2356edd877..65c7bc48647 100644 --- a/Sources/Sentry/SentryMigrateSessionInit.m +++ b/Sources/Sentry/SentryMigrateSessionInit.m @@ -24,7 +24,7 @@ + (BOOL)migrateSessionInit:(nullable SentryEnvelope *)envelope @"Could not migrate session init, because the envelope item has no data."); continue; } - SentrySession *session = [DataDeserialization sessionWithData:data]; + SentrySession *session = [SentrySerializationSwift sessionWithData:data]; if (nil != session && [session.flagInit boolValue]) { BOOL didSetInitFlag = [self setInitFlagOnNextEnvelopeWithSameSessionId:session @@ -56,7 +56,7 @@ + (BOOL)setInitFlagOnNextEnvelopeWithSameSessionId:(SentrySession *)session continue; } - SentryEnvelope *envelope = [DataDeserialization envelopeWithData:envelopeData]; + SentryEnvelope *envelope = [SentrySerializationSwift envelopeWithData:envelopeData]; if (nil != envelope) { BOOL didSetInitFlag = [self setInitFlagIfContainsSameSessionId:session.sessionId @@ -84,7 +84,7 @@ + (BOOL)setInitFlagIfContainsSameSessionId:(NSUUID *)sessionId @"Could not migrate session init, because the envelope item has no data."); continue; } - SentrySession *localSession = [DataDeserialization sessionWithData:data]; + SentrySession *localSession = [SentrySerializationSwift sessionWithData:data]; if (nil != localSession && [localSession.sessionId isEqual:sessionId]) { [localSession setFlagInit]; diff --git a/Sources/Swift/Helper/DataDeserialization.swift b/Sources/Swift/Helper/SentrySerializationSwift.swift similarity index 97% rename from Sources/Swift/Helper/DataDeserialization.swift rename to Sources/Swift/Helper/SentrySerializationSwift.swift index 2b6b3bc6474..1a6bf5959b5 100644 --- a/Sources/Swift/Helper/DataDeserialization.swift +++ b/Sources/Swift/Helper/SentrySerializationSwift.swift @@ -1,6 +1,6 @@ @_implementationOnly import _SentryPrivate -@_spi(Private) @objc public final class DataDeserialization: NSObject { +@_spi(Private) @objc public final class SentrySerializationSwift: NSObject { @objc(sessionWithData:) public static func session(with data: Data) -> SentrySession? { do { guard let sessionDictionary = try JSONSerialization.jsonObject(with: data) as? [String: Any], @@ -34,12 +34,13 @@ //swiftlint:disable cyclomatic_complexity function_body_length @objc(envelopeWithData:) public static func envelope(with data: Data) -> SentryEnvelope? { + let newline = UInt8(ascii: "\n") var envelopeHeader: SentryEnvelopeHeader? let bytes = [UInt8](data) var envelopeHeaderIndex: Int = 0 for i in 0.. 3 { let unzippedBody = try XCTUnwrap(sentry_unzippedData(XCTUnwrap(sessionRequest.httpBody))) let requestUnzippedBody = try XCTUnwrap(sentry_unzippedData(XCTUnwrap(XCTUnwrap(fixture.requestManager.requests.invocations.element(at: 3)).httpBody))) - let actualEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: unzippedBody)) - let expectedEnvelope = try XCTUnwrap(DataDeserialization.envelope(with: requestUnzippedBody)) + let actualEnvelope = try XCTUnwrap(SentrySerializationSwift.envelope(with: unzippedBody)) + let expectedEnvelope = try XCTUnwrap(SentrySerializationSwift.envelope(with: requestUnzippedBody)) try EnvelopeUtils.assertEnvelope(expected: expectedEnvelope, actual: actualEnvelope) } else { XCTFail("Expected a fourth invocation") diff --git a/Tests/SentryTests/SentryHubTests.swift b/Tests/SentryTests/SentryHubTests.swift index f6274dd5795..28342a7c791 100644 --- a/Tests/SentryTests/SentryHubTests.swift +++ b/Tests/SentryTests/SentryHubTests.swift @@ -1512,7 +1512,7 @@ class SentryHubTests: XCTestCase { XCTAssertEqual(1, fixture.client.captureEnvelopeInvocations.count) let envelope = fixture.client.captureEnvelopeInvocations.first! XCTAssertEqual(2, envelope.items.count) - let session = DataDeserialization.session(with: try XCTUnwrap(XCTUnwrap(envelope.items.element(at: 1)).data)) + let session = SentrySerializationSwift.session(with: try XCTUnwrap(XCTUnwrap(envelope.items.element(at: 1)).data)) XCTAssertEqual(1, session?.errors) } diff --git a/Tests/SentryTests/SentrySDKTests.swift b/Tests/SentryTests/SentrySDKTests.swift index 635ed57aba1..cda8a569b67 100644 --- a/Tests/SentryTests/SentrySDKTests.swift +++ b/Tests/SentryTests/SentrySDKTests.swift @@ -426,7 +426,7 @@ class SentrySDKTests: XCTestCase { // Assert let eventEnvelopeItems = try fileManager.getAllEnvelopes().map { fileContent in - return try XCTUnwrap(DataDeserialization.envelope(with: fileContent.contents)) + return try XCTUnwrap(SentrySerializationSwift.envelope(with: fileContent.contents)) }.flatMap { envelope in return envelope.items.filter { $0.header.type == SentryEnvelopeItemTypes.event } }