-
-
Notifications
You must be signed in to change notification settings - Fork 372
refactor: Migrate SentryMsgPackSerializer from Objective-C to Swift #6143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
philprime
wants to merge
20
commits into
main
Choose a base branch
from
philprime/msg-pack-serializer-null-handling
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
a121c88
refactor: Add nullability-handling to SentryMsgPackSerializer with co…
philprime 6eb1e43
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime 3021956
feat: Enhance SentryMsgPackSerializer with error handling and additio…
philprime 810f132
refactor: Remove SentryMsgPackSerializer and migrate to Swift impleme…
philprime b73f131
fix: Update keyData handling in SentryMsgPackSerializer to use try fo…
philprime 8d45ba3
feat: Add TestStreamableObject for enhanced serialization testing
philprime 9b91dac
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime 7726ae1
fix potential overflow for very large files
philprime 2307fe1
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime 5181e9e
remove legacy extensions
philprime 568d287
resolve conversion mistakes
philprime 157de83
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime e7abc0a
Fix xcode proj
philprime 0442fd3
Refactor SentryMsgPackSerializer to improve file serialization and er…
philprime e6ec86d
Merge branch 'main' into philprime/msg-pack-serializer-null-handling
philprime dfb092e
Merge origin/main into msg-pack-serializer-null-handling branch
philprime fac2f0b
refactor: Address PR feedback for SentryMsgPackSerializer
philprime b96365a
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime b7a63fb
applied fixes
philprime 014d2cf
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| @testable import Sentry | ||
|
|
||
| private class ErrorInputStream: InputStream { | ||
| override var hasBytesAvailable: Bool { | ||
| return true | ||
| } | ||
|
|
||
| override func read(_ buffer: UnsafeMutablePointer<UInt8>, maxLength len: Int) -> Int { | ||
| return -1 // Simulate read error | ||
| } | ||
|
|
||
| override func open() { | ||
| // No-op | ||
| } | ||
|
|
||
| override func close() { | ||
| // No-op | ||
| } | ||
| } | ||
|
|
||
| public class TestStreamableObject: NSObject, SentryStreamable { | ||
|
|
||
| private let shouldReturnNilInputStream: Bool | ||
| private let streamSizeValue: UInt? | ||
| private let shouldReturnErrorStream: Bool | ||
|
|
||
| public init(streamSize: UInt?, shouldReturnNilInputStream: Bool, shouldReturnErrorStream: Bool = false) { | ||
| self.streamSizeValue = streamSize | ||
| self.shouldReturnNilInputStream = shouldReturnNilInputStream | ||
| self.shouldReturnErrorStream = shouldReturnErrorStream | ||
| super.init() | ||
| } | ||
|
|
||
| public func asInputStream() -> InputStream? { | ||
| if shouldReturnNilInputStream { | ||
| return nil | ||
| } | ||
| if shouldReturnErrorStream { | ||
| return ErrorInputStream() | ||
| } | ||
| return InputStream(data: Data()) | ||
| } | ||
|
|
||
| public func streamSize() -> UInt? { | ||
| return streamSizeValue | ||
| } | ||
|
|
||
| // MARK: - Convenience factory methods for common test scenarios | ||
|
|
||
| public static func objectWithNilInputStream() -> TestStreamableObject { | ||
| return TestStreamableObject(streamSize: 10, shouldReturnNilInputStream: true) | ||
| } | ||
|
|
||
| public static func objectWithZeroSize() -> TestStreamableObject { | ||
| return TestStreamableObject(streamSize: 0, shouldReturnNilInputStream: false) | ||
| } | ||
|
|
||
| public static func objectWithNegativeSize() -> TestStreamableObject { | ||
| return TestStreamableObject(streamSize: nil, shouldReturnNilInputStream: false) | ||
| } | ||
|
|
||
| public static func objectWithErrorStream() -> TestStreamableObject { | ||
| return TestStreamableObject(streamSize: 10, shouldReturnNilInputStream: false, shouldReturnErrorStream: true) | ||
| } | ||
|
|
||
| public static func objectWithZeroBytesRead() -> TestStreamableObject { | ||
| return TestStreamableObject(streamSize: 10, shouldReturnNilInputStream: false, shouldReturnErrorStream: false) | ||
| } | ||
|
|
||
| public static func objectWithLargeSize() -> TestStreamableObject { | ||
| // Return size larger than UInt32.max to test truncation | ||
| return TestStreamableObject( | ||
| streamSize: UInt.max, | ||
| shouldReturnNilInputStream: false, | ||
| shouldReturnErrorStream: false | ||
| ) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| extension Data: SentryStreamable { | ||
| func asInputStream() -> InputStream? { | ||
| return InputStream(data: self) | ||
| } | ||
|
|
||
| func streamSize() -> UInt? { | ||
| return UInt(self.count) | ||
| } | ||
| } |
104 changes: 104 additions & 0 deletions
104
Sources/Swift/Tools/MsgPack/SentryMsgPackSerializer.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| /** | ||
| * This is a partial implementation of the MessagePack format. | ||
| * We only need to concatenate a list of NSData into an envelope item. | ||
| */ | ||
| class SentryMsgPackSerializer { | ||
| @objc | ||
philprime marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| static func serializeDictionary(toMessagePack dictionary: [String: Any], intoFile fileURL: URL) -> Bool { | ||
philprime marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| do { | ||
| try serializeToFile(dictionary: dictionary, fileURL: fileURL) | ||
| return true | ||
| } catch { | ||
| SentrySDKLog.error("Failed to serialize dictionary to MessagePack - Error: \(error)") | ||
| // Clean up partial file on error | ||
| do { | ||
| try FileManager.default.removeItem(at: fileURL) | ||
| } catch { | ||
| // Ignore cleanup errors - file might not exist | ||
| } | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| // swiftlint:disable:next function_body_length cyclomatic_complexity | ||
| private static func serializeToFile(dictionary: [String: Any], fileURL: URL) throws { | ||
philprime marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| guard let outputStream = OutputStream(url: fileURL, append: false) else { | ||
| throw SentryMsgPackSerializerError.outputError("Failed to create output stream for file: \(fileURL)") | ||
| } | ||
| outputStream.open() | ||
| defer { | ||
| outputStream.close() | ||
| } | ||
|
|
||
| // Check if stream opened successfully | ||
| if outputStream.streamError != nil { | ||
| throw SentryMsgPackSerializerError.outputError("Failed to open output stream for file: \(fileURL)") | ||
| } | ||
|
|
||
| let mapHeader = UInt8(truncatingIfNeeded: 0x80 | dictionary.count) // Map up to 15 elements | ||
| _ = outputStream.write([mapHeader], maxLength: 1) | ||
|
|
||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| for (key, anyValue) in dictionary { | ||
philprime marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| guard let value = anyValue as? SentryStreamable else { | ||
| throw SentryMsgPackSerializerError.invalidValue("Value does not conform to SentryStreamable: \(anyValue)") | ||
| } | ||
| guard let keyData = key.data(using: .utf8) else { | ||
philprime marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| throw SentryMsgPackSerializerError.invalidInput("Could not encode key as UTF-8: \(key)") | ||
| } | ||
|
|
||
| let str8Header: UInt8 = 0xD9 // String up to 255 characters | ||
| let keyLength = UInt8(truncatingIfNeeded: keyData.count) // Truncates if > 255, matching Objective-C behavior | ||
| _ = outputStream.write([str8Header], maxLength: 1) | ||
| _ = outputStream.write([keyLength], maxLength: 1) | ||
|
|
||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| keyData.withUnsafeBytes { bytes in | ||
| guard let bufferAddress = bytes.bindMemory(to: UInt8.self).baseAddress else { | ||
| return | ||
| } | ||
| _ = outputStream.write(bufferAddress, maxLength: keyData.count) | ||
| } | ||
|
|
||
| guard let dataLength = value.streamSize(), dataLength > 0 else { | ||
| // MsgPack is being used strictly for session replay. | ||
| // An item with a length of 0 will not be useful. | ||
| // If we plan to use MsgPack for something else, | ||
| // this needs to be re-evaluated. | ||
| throw SentryMsgPackSerializerError.emptyData("Data for MessagePack dictionary has no content - Input: \(value)") | ||
| } | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| let valueLength = UInt32(truncatingIfNeeded: dataLength) | ||
| // We will always use the 4 bytes data length for simplicity. | ||
| // Worst case we're losing 3 bytes. | ||
| let bin32Header: UInt8 = 0xC6 | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| _ = outputStream.write([bin32Header], maxLength: 1) | ||
|
|
||
| // Write UInt32 as big endian bytes | ||
| let lengthBytes = [ | ||
| UInt8((valueLength >> 24) & 0xFF), | ||
philprime marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| UInt8((valueLength >> 16) & 0xFF), | ||
| UInt8((valueLength >> 8) & 0xFF), | ||
| UInt8(valueLength & 0xFF) | ||
| ] | ||
| _ = outputStream.write(lengthBytes, maxLength: 4) | ||
|
|
||
| guard let inputStream = value.asInputStream() else { | ||
| throw SentryMsgPackSerializerError.streamError("Could not get input stream - Input: \(value)") | ||
| } | ||
|
|
||
| inputStream.open() | ||
| defer { inputStream.close() } | ||
|
|
||
| var buffer = [UInt8](repeating: 0, count: 1_024) | ||
| var bytesRead: Int | ||
|
|
||
| while inputStream.hasBytesAvailable { | ||
| bytesRead = inputStream.read(&buffer, maxLength: buffer.count) | ||
| if bytesRead > 0 { | ||
| _ = outputStream.write(buffer, maxLength: bytesRead) | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } else if bytesRead < 0 { | ||
| throw SentryMsgPackSerializerError.streamError("Error reading bytes from input stream - Input: \(value) - Bytes read: \(bytesRead)") | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
8 changes: 8 additions & 0 deletions
8
Sources/Swift/Tools/MsgPack/SentryMsgPackSerializerError.swift
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| enum SentryMsgPackSerializerError: Error { | ||
| case dictionaryTooLarge | ||
| case invalidValue(String) | ||
| case invalidInput(String) | ||
| case emptyData(String) | ||
| case streamError(String) | ||
| case outputError(String) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| protocol SentryStreamable { | ||
| func asInputStream() -> InputStream? | ||
| func streamSize() -> UInt? | ||
| } | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| extension URL: SentryStreamable { | ||
| func asInputStream() -> InputStream? { | ||
| return InputStream(url: self) | ||
| } | ||
|
|
||
| func streamSize() -> UInt? { | ||
| let attributes: [FileAttributeKey: Any] | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| do { | ||
| attributes = try FileManager.default.attributesOfItem(atPath: path) | ||
| } catch { | ||
| SentrySDKLog.error("Could not read file attributes - File: \(self) - Error: \(error)") | ||
philprime marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| return nil | ||
| } | ||
| guard let fileSize = attributes[.size] as? NSNumber else { | ||
| SentrySDKLog.error("Could not read file size attribute - File: \(self)") | ||
| return nil | ||
| } | ||
| return fileSize.uintValue | ||
| } | ||
philprime marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.