Skip to content
Draft
Show file tree
Hide file tree
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 Sep 11, 2025
6eb1e43
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime Sep 15, 2025
3021956
feat: Enhance SentryMsgPackSerializer with error handling and additio…
philprime Sep 15, 2025
810f132
refactor: Remove SentryMsgPackSerializer and migrate to Swift impleme…
philprime Sep 15, 2025
b73f131
fix: Update keyData handling in SentryMsgPackSerializer to use try fo…
philprime Sep 15, 2025
8d45ba3
feat: Add TestStreamableObject for enhanced serialization testing
philprime Sep 23, 2025
9b91dac
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime Sep 23, 2025
7726ae1
fix potential overflow for very large files
philprime Sep 23, 2025
2307fe1
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime Oct 9, 2025
5181e9e
remove legacy extensions
philprime Oct 9, 2025
568d287
resolve conversion mistakes
philprime Oct 9, 2025
157de83
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime Oct 21, 2025
e7abc0a
Fix xcode proj
philprime Oct 21, 2025
0442fd3
Refactor SentryMsgPackSerializer to improve file serialization and er…
philprime Oct 21, 2025
e6ec86d
Merge branch 'main' into philprime/msg-pack-serializer-null-handling
philprime Oct 23, 2025
dfb092e
Merge origin/main into msg-pack-serializer-null-handling branch
philprime Nov 3, 2025
fac2f0b
refactor: Address PR feedback for SentryMsgPackSerializer
philprime Nov 3, 2025
b96365a
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime Nov 3, 2025
b7a63fb
applied fixes
philprime Nov 3, 2025
014d2cf
Merge remote-tracking branch 'origin/main' into philprime/msg-pack-se…
philprime Nov 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions Sentry.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions SentryTestUtils/TestStreamableObject.swift
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
)
}
}
1 change: 0 additions & 1 deletion Sources/Sentry/SentryClient.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
#import "SentryMechanismMeta.h"
#import "SentryMessage.h"
#import "SentryMeta.h"
#import "SentryMsgPackSerializer.h"
#import "SentryNSDictionarySanitize.h"
#import "SentryNSError.h"
#import "SentryOptions+Private.h"
Expand Down
108 changes: 0 additions & 108 deletions Sources/Sentry/SentryMsgPackSerializer.m

This file was deleted.

31 changes: 0 additions & 31 deletions Sources/Sentry/include/SentryMsgPackSerializer.h

This file was deleted.

1 change: 0 additions & 1 deletion Sources/Sentry/include/SentryPrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@
#import "SentryLevelHelper.h"
#import "SentryMeta.h"
#import "SentryModels+Serializable.h"
#import "SentryMsgPackSerializer.h"
#import "SentryNSDictionarySanitize.h"
#import "SentryOptions+Private.h"
#import "SentryPerformanceTracker.h"
Expand Down
9 changes: 9 additions & 0 deletions Sources/Swift/Tools/MsgPack/Data+SentryStreamable.swift
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 Sources/Swift/Tools/MsgPack/SentryMsgPackSerializer.swift
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
static func serializeDictionary(toMessagePack dictionary: [String: Any], intoFile fileURL: URL) -> Bool {
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 {
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)

for (key, anyValue) in dictionary {
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 {
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)

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)")
}

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
_ = outputStream.write([bin32Header], maxLength: 1)

// Write UInt32 as big endian bytes
let lengthBytes = [
UInt8((valueLength >> 24) & 0xFF),
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)
} else if bytesRead < 0 {
throw SentryMsgPackSerializerError.streamError("Error reading bytes from input stream - Input: \(value) - Bytes read: \(bytesRead)")
}
}
}
}
}
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)
}
4 changes: 4 additions & 0 deletions Sources/Swift/Tools/MsgPack/SentryStreamable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
protocol SentryStreamable {
func asInputStream() -> InputStream?
func streamSize() -> UInt?
}
20 changes: 20 additions & 0 deletions Sources/Swift/Tools/MsgPack/URL+SentryStreamable.swift
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]
do {
attributes = try FileManager.default.attributesOfItem(atPath: path)
} catch {
SentrySDKLog.error("Could not read file attributes - File: \(self) - Error: \(error)")
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
}
}
6 changes: 3 additions & 3 deletions Sources/Swift/Tools/SentryEnvelopeItem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,9 @@
let envelopeContentUrl = video.deletingPathExtension().appendingPathExtension("dat")

let pack: [String: SentryStreamable] = [
"replay_event": replayEventData as NSData,
"replay_recording": recording as NSData,
"replay_video": video as NSURL
"replay_event": replayEventData,
"replay_recording": recording,
"replay_video": video
]
let success = SentryMsgPackSerializer.serializeDictionary(toMessagePack:
pack,
Expand Down
Loading
Loading