Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ do {

```swift
import CBOR
import Foundation

// Define your data structures
struct Person: Codable {
Expand Down
29 changes: 11 additions & 18 deletions Sources/CBOR/CBOR.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif

#if canImport(Darwin)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#elseif os(Windows)
import ucrt
#endif

// MARK: - CBOR Type

/// A CBOR value
public indirect enum CBOR: Equatable {
public indirect enum CBOR: Equatable, Sendable {
/// A positive unsigned integer
case unsignedInt(UInt64)
/// A negative integer
Expand Down Expand Up @@ -49,7 +45,7 @@ public indirect enum CBOR: Equatable {
}

/// Decodes a CBOR value from bytes
public static func decode(_ bytes: [UInt8]) throws -> CBOR {
public static func decode(_ bytes: [UInt8]) throws(CBORError) -> CBOR {
var reader = CBORReader(data: bytes)
let value = try _decode(reader: &reader)

Expand All @@ -67,7 +63,7 @@ public indirect enum CBOR: Equatable {
/// - Parameters:
/// - key: The key of the pair
/// - value: The value of the pair
public struct CBORMapPair: Equatable {
public struct CBORMapPair: Equatable, Sendable {
public let key: CBOR
public let value: CBOR

Expand Down Expand Up @@ -103,11 +99,9 @@ private func _encode(_ value: CBOR, into output: inout [UInt8]) {
encodeUnsigned(major: 2, value: UInt64(bytes.count), into: &output)
output.append(contentsOf: bytes)
case .textString(let string):
if let utf8 = string.data(using: .utf8) {
let bytes = [UInt8](utf8)
encodeUnsigned(major: 3, value: UInt64(bytes.count), into: &output)
output.append(contentsOf: bytes)
}
let bytes = [UInt8](string.utf8)
encodeUnsigned(major: 3, value: UInt64(bytes.count), into: &output)
output.append(contentsOf: bytes)
case .array(let array):
encodeUnsigned(major: 4, value: UInt64(array.count), into: &output)
for item in array {
Expand Down Expand Up @@ -138,8 +132,7 @@ private func _encode(_ value: CBOR, into output: inout [UInt8]) {
case .float(let f):
// Encode as IEEE 754 double-precision float
output.append(0xfb)
var value = f
withUnsafeBytes(of: &value) { bytes in
withUnsafeBytes(of: f) { bytes in
// Append bytes in big-endian order
for i in (0..<8).reversed() {
output.append(bytes[i])
Expand Down Expand Up @@ -192,7 +185,7 @@ private func encodeUnsigned(major: UInt8, value: UInt64, into output: inout [UIn
/// - reader: The reader to decode from
/// - Returns: The decoded CBOR value
/// - Throws: A `CBORError` if the decoding fails
private func _decode(reader: inout CBORReader) throws -> CBOR {
private func _decode(reader: inout CBORReader) throws(CBORError) -> CBOR {
let initial = try reader.readByte()

// Check for break marker (0xff)
Expand Down Expand Up @@ -321,7 +314,7 @@ private func _decode(reader: inout CBORReader) throws -> CBOR {
}

/// Reads an unsigned integer value based on the additional information.
private func readUIntValue(additional: UInt8, reader: inout CBORReader) throws -> UInt64 {
private func readUIntValue(additional: UInt8, reader: inout CBORReader) throws(CBORError) -> UInt64 {
// Check for indefinite length first
if additional == 31 {
throw CBORError.indefiniteLengthNotSupported
Expand Down
2 changes: 2 additions & 0 deletions Sources/CBOR/CBORCodable.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if !hasFeature(Embedded)
#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
Expand Down Expand Up @@ -155,3 +156,4 @@ struct CBORKey: CodingKey {
self.intValue = index
}
}
#endif
2 changes: 2 additions & 0 deletions Sources/CBOR/CBORDecoder.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if !hasFeature(Embedded)
#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
Expand Down Expand Up @@ -1917,3 +1918,4 @@ private struct CBORSingleValueDecodingContainer: SingleValueDecodingContainer {
return try T(from: decoder)
}
}
#endif
2 changes: 2 additions & 0 deletions Sources/CBOR/CBOREncoder.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#if !hasFeature(Embedded)
#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
Expand Down Expand Up @@ -771,3 +772,4 @@ private struct CBOREncoderSingleValueContainer: SingleValueEncodingContainer {
try encoder.push(encoder.encodeCBOR(value))
}
}
#endif
73 changes: 9 additions & 64 deletions Sources/CBOR/CBORError.swift
Original file line number Diff line number Diff line change
@@ -1,75 +1,24 @@
#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

// MARK: - Error Types

/// Errors that can occur during CBOR encoding and decoding.
///
/// These errors provide detailed information about what went wrong during
/// CBOR processing operations, helping developers diagnose and fix issues
/// in their CBOR data or usage of the CBOR API.
public enum CBORError: Error {
public enum CBORError: Error, Equatable, Sendable {
/// The input data is not valid CBOR.
///
/// This error occurs when the decoder encounters data that doesn't conform to
/// the CBOR specification (RFC 8949). This could be due to corrupted data,
/// incomplete data, or data encoded with a different format entirely.
case invalidCBOR

/// Expected a specific type but found another.
///
/// This error occurs when trying to decode a CBOR value as a specific type,
/// but the actual type of the value doesn't match the expected type.
/// - Parameters:
/// - expected: The type that was expected (e.g., "String", "Int", "Array")
/// - actual: The actual type that was found in the CBOR data
case typeMismatch(expected: String, actual: String)

/// Array index out of bounds.
///
/// This error occurs when attempting to access an element in a CBOR array
/// using an index that is outside the valid range for the array.
/// - Parameters:
/// - index: The requested index that was attempted to be accessed
/// - count: The actual number of elements in the array (valid indices are 0..<count)
case outOfBounds(index: Int, count: Int)

/// Required key missing from map.
///
/// This error occurs when trying to decode a CBOR map into a Swift struct or class,
/// but a required key is not present in the map.
/// - Parameter key: The name of the missing key
case missingKey(String)

/// Value conversion failed.
///
/// This error occurs when a CBOR value cannot be converted to the requested Swift type,
/// even though the CBOR type is compatible with the requested type.
/// - Parameter message: A description of what went wrong during the conversion
case valueConversionFailed(String)

/// Invalid UTF-8 string data.
///
/// This error occurs when decoding a CBOR text string that contains invalid UTF-8 sequences.
/// All CBOR text strings must contain valid UTF-8 data according to the specification.
case invalidUTF8

/// Integer overflow during encoding/decoding.
///
/// This error occurs when a CBOR integer value is too large to fit into the
/// corresponding Swift integer type (e.g., trying to decode a UInt64.max into an Int).
case integerOverflow

/// Tag value is not supported.
///
/// This error occurs when the decoder encounters a CBOR tag that is not supported
/// by the current implementation.
/// - Parameter tag: The unsupported tag number
case unsupportedTag(UInt64)

/// Reached end of data while decoding.
///
/// This error occurs when the decoder unexpectedly reaches the end of the input data
Expand Down Expand Up @@ -105,26 +54,15 @@ public enum CBORError: Error {
case extraDataFound
}

@_unavailableInEmbedded
extension CBORError: CustomStringConvertible {
/// A human-readable description of the error.
public var description: String {
switch self {
case .invalidCBOR:
return "Invalid CBOR data: The input does not conform to the CBOR specification (RFC 8949)"
case .typeMismatch(let expected, let actual):
return "Type mismatch: expected \(expected), found \(actual)"
case .outOfBounds(let index, let count):
return "Array index out of bounds: attempted to access index \(index), but array only contains \(count) elements (valid indices are 0..<\(count))"
case .missingKey(let key):
return "Missing key: required key '\(key)' was not found in the CBOR map"
case .valueConversionFailed(let message):
return "Value conversion failed: \(message)"
case .invalidUTF8:
return "Invalid UTF-8 data: the CBOR text string contains invalid UTF-8 sequences"
case .integerOverflow:
return "Integer overflow: the CBOR integer value is too large for the target Swift integer type"
case .unsupportedTag(let tag):
return "Unsupported tag: tag \(tag) is not supported by this implementation"
case .prematureEnd:
return "Unexpected end of data: reached the end of input before completing the CBOR value"
case .invalidInitialByte(let byte):
Expand All @@ -139,6 +77,13 @@ extension CBORError: CustomStringConvertible {
}
}

#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

@_unavailableInEmbedded
extension CBORError: LocalizedError {
public var errorDescription: String? {
return description
Expand Down
11 changes: 3 additions & 8 deletions Sources/CBOR/CBORReader.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,3 @@
#if canImport(FoundationEssentials)
import FoundationEssentials
#elseif canImport(Foundation)
import Foundation
#endif

/// A helper struct for reading CBOR data byte by byte
struct CBORReader {
Expand All @@ -15,7 +10,7 @@ struct CBORReader {
}

/// Read a single byte from the input
mutating func readByte() throws -> UInt8 {
mutating func readByte() throws(CBORError) -> UInt8 {
guard index < data.count else {
throw CBORError.prematureEnd
}
Expand All @@ -25,7 +20,7 @@ struct CBORReader {
}

/// Read a specified number of bytes from the input
mutating func readBytes(_ count: Int) throws -> [UInt8] {
mutating func readBytes(_ count: Int) throws(CBORError) -> [UInt8] {
guard index + count <= data.count else {
throw CBORError.prematureEnd
}
Expand All @@ -50,7 +45,7 @@ struct CBORReader {
}

/// Skip a specified number of bytes
mutating func skip(_ count: Int) throws {
mutating func skip(_ count: Int) throws(CBORError) {
guard index + count <= data.count else {
throw CBORError.prematureEnd
}
Expand Down
Loading
Loading