diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..4651d99 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,15 @@ +{ + "originHash" : "1658920b6ff75b4d90f9cc1d550887ae5383d8a4f1bb602d37aa7b303cab1228", + "pins" : [ + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "9bf03ff58ce34478e66aaee630e491823326fd06", + "version" : "1.1.3" + } + } + ], + "version" : 3 +} diff --git a/Package.swift b/Package.swift index 1ed26ec..d9a29cf 100644 --- a/Package.swift +++ b/Package.swift @@ -1,20 +1,24 @@ -// swift-tools-version:5.5 +// swift-tools-version:6.0 import PackageDescription let package = Package( - name: "SwiftCBOR", - platforms: [.macOS(.v10_13), .iOS(.v13), .tvOS(.v13), .macCatalyst(.v13), .watchOS(.v8)], - products: [ - .library(name: "SwiftCBOR", targets: ["SwiftCBOR"]) - ], - targets: [ - .target(name: "SwiftCBOR", path: "Sources", exclude: ["Info.plist"]), - .testTarget( - name: "SwiftCBORTests", - dependencies: ["SwiftCBOR"], - path: "Tests", - exclude: ["Info.plist"] - ) - ] + name: "SwiftCBOR", + platforms: [.macOS(.v10_13), .iOS(.v13), .tvOS(.v13), .macCatalyst(.v13), .watchOS(.v8)], + products: [ + .library(name: "SwiftCBOR", targets: ["SwiftCBOR"]) + ], + dependencies: [ + .package(url: "https://github.com/apple/swift-collections.git", from: "1.1.3"), + ], + targets: [ + .target(name: "SwiftCBOR", dependencies: [.product(name: "Collections", package: "swift-collections")], + path: "Sources", exclude: ["Info.plist"]), + .testTarget( + name: "SwiftCBORTests", + dependencies: ["SwiftCBOR"], + path: "Tests", + exclude: ["Info.plist"] + ) + ] ) diff --git a/README.md b/README.md index 67a7420..e817766 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,10 @@ [![unlicense](https://img.shields.io/badge/un-license-green.svg?style=flat)](http://unlicense.org) +## SwiftCBOR (Scytales clone) + # SwiftCBOR +This is a fork of the original SwiftCBOR library based on the following [PR #96](https://github.com/valpackett/SwiftCBOR/pull/96) . The original library is available [here](https://github.com/valpackett/SwiftCBOR) + A [CBOR (RFC 7049 Concise Binary Object Representation)](http://cbor.io) decoder and encoder in Swift. Encode directly from Swift types or use a wrapper object. Decode to a CBOR value type that can be accessed with native Swift subscripting and expressed with the equivalent literal notation. diff --git a/Sources/CBOR.swift b/Sources/CBOR.swift index e5270bc..c59b9f4 100644 --- a/Sources/CBOR.swift +++ b/Sources/CBOR.swift @@ -1,8 +1,9 @@ #if canImport(Foundation) import Foundation #endif +import OrderedCollections -public indirect enum CBOR : Equatable, Hashable, +public indirect enum CBOR : Equatable, Hashable, Sendable, ExpressibleByNilLiteral, ExpressibleByIntegerLiteral, ExpressibleByStringLiteral, ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral, ExpressibleByBooleanLiteral, ExpressibleByFloatLiteral { @@ -12,7 +13,7 @@ public indirect enum CBOR : Equatable, Hashable, case byteString([UInt8]) case utf8String(String) case array([CBOR]) - case map([CBOR : CBOR]) + case map(OrderedDictionary) case tagged(Tag, CBOR) case simple(UInt8) case boolean(Bool) @@ -84,7 +85,7 @@ public indirect enum CBOR : Equatable, Hashable, public init(stringLiteral value: String) { self = .utf8String(value) } public init(arrayLiteral elements: CBOR...) { self = .array(elements) } public init(dictionaryLiteral elements: (CBOR, CBOR)...) { - var result = [CBOR : CBOR]() + var result = OrderedDictionary() for (key, value) in elements { result[key] = value } diff --git a/Sources/CBORDecoder.swift b/Sources/CBORDecoder.swift index 4e26806..97c88ba 100644 --- a/Sources/CBORDecoder.swift +++ b/Sources/CBORDecoder.swift @@ -1,6 +1,7 @@ #if canImport(Foundation) import Foundation #endif +import OrderedCollections public enum CBORError : Error { case unfinishedSequence @@ -81,8 +82,8 @@ public class CBORDecoder { return result } - private func readNPairs(_ n: Int) throws -> [CBOR : CBOR] { - var result: [CBOR: CBOR] = [:] + private func readNPairs(_ n: Int) throws -> OrderedDictionary { + var result = OrderedDictionary() for _ in (0.. [CBOR : CBOR] { - var result: [CBOR: CBOR] = [:] + func readPairsUntilBreak() throws -> OrderedDictionary { + var result = OrderedDictionary() var key = try decodeItem() if key == CBOR.break { return result diff --git a/Sources/CBOREncodable.swift b/Sources/CBOREncodable.swift index 50cff6b..19a520f 100644 --- a/Sources/CBOREncodable.swift +++ b/Sources/CBOREncodable.swift @@ -1,6 +1,7 @@ #if canImport(Foundation) import Foundation #endif +import OrderedCollections public protocol CBOREncodable { /// Optional function that can potentially serve as an opportunity to optimize encoding. @@ -25,7 +26,7 @@ extension CBOR: CBOREncodable { case let .byteString(bs): return CBOR.encodeByteString(bs, options: options) case let .utf8String(str): return str.encode(options: options) case let .array(a): return CBOR.encodeArray(a, options: options) - case let .map(m): return CBOR.encodeMap(m, options: options) + case let .map(m): return CBOR.encodeCBORMap(m, options: options) #if canImport(Foundation) case let .date(d): return CBOR.encodeDate(d, options: options) #endif @@ -210,10 +211,21 @@ extension Dictionary where Key: CBOREncodable, Value: CBOREncodable { } public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR { - return CBOR.map(Dictionary(uniqueKeysWithValues: self.map { ($0.key.toCBOR(options: options), $0.value.toCBOR(options: options)) })) + return CBOR.map(OrderedDictionary(uniqueKeysWithValues: self.map { ($0.key.toCBOR(options: options), $0.value.toCBOR(options: options)) })) } } + +extension OrderedDictionary where Key: CBOREncodable, Value: CBOREncodable { + public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] { + return CBOR.encodeCBORMap(OrderedDictionary(uniqueKeysWithValues: self.map { ($0.key.toCBOR(options: options), $0.value.toCBOR(options: options)) }), options: options) + } + + public func toCBOR(options: CBOROptions = CBOROptions()) -> CBOR { + return CBOR.map(OrderedDictionary(uniqueKeysWithValues: self.map { ($0.key.toCBOR(options: options), $0.value.toCBOR(options: options)) })) + } +} + extension Optional where Wrapped: CBOREncodable { public func encode(options: CBOROptions = CBOROptions()) -> [UInt8] { switch self { diff --git a/Sources/CBOREncoder.swift b/Sources/CBOREncoder.swift index d33a355..f9f9ca4 100644 --- a/Sources/CBOREncoder.swift +++ b/Sources/CBOREncoder.swift @@ -1,6 +1,7 @@ #if canImport(Foundation) import Foundation #endif +import OrderedCollections let isBigEndian = Int(bigEndian: 42) == 42 @@ -170,6 +171,14 @@ extension CBOR { try CBOR.encodeMap(map, into: &res, options: options) return res } + + public static func encodeCBORMap(_ map: OrderedDictionary, options: CBOROptions = CBOROptions()) -> [UInt8] { + var res: [UInt8] = [] + res = map.count.encode(options: options) + res[0] = res[0] | 0b101_00000 + CBOR.encodeCBORMap(map, into: &res, options: options) + return res + } // MARK: - major 6: tagged values @@ -273,7 +282,8 @@ extension CBOR { let (integral, fractional) = modf(timeInterval) let seconds = Int64(integral) - let nanoseconds = Int32(fractional * Double(NSEC_PER_SEC)) + // The NSEC_PER_SEC value is 1,000,000,000 (one billion), representing the number of nanoseconds in one second. + let nanoseconds = Int32(fractional * Double(1_000_000_000)) switch options.dateStrategy { case .annotatedMap: @@ -288,7 +298,7 @@ extension CBOR { dateCBOR = CBOR.unsignedInt(UInt64(seconds)) } - let map: [String: CBOREncodable] = [ + let map: Dictionary = [ AnnotatedMapDateStrategy.typeKey: AnnotatedMapDateStrategy.typeValue, AnnotatedMapDateStrategy.valueKey: dateCBOR ] @@ -422,7 +432,7 @@ extension CBOR { return try CBOR.array(anyArr.map { try cborFromAny($0) }) case is [String: Any?]: let anyMap = any as! [String: Any?] - return try CBOR.map(Dictionary( + return try CBOR.map(OrderedDictionary( uniqueKeysWithValues: anyMap.map { try (cborFromAny($0.key), cborFromAny($0.value)) } )) case is Void: @@ -479,6 +489,17 @@ extension CBOR { } } } + + private static func encodeCBORMap(_ map: OrderedDictionary, into res: inout [UInt8], options: CBOROptions = CBOROptions()) { + let sortedKeysWithEncodedKeys = map.keys + + sortedKeysWithEncodedKeys.forEach { keyTuple in + let encodedKey = keyTuple.encode(options: options) + res.append(contentsOf: encodedKey) + let encodedVal = map[keyTuple]!.encode(options: options) + res.append(contentsOf: encodedVal) + } + } } public enum CBOREncoderError: Error { diff --git a/Tests/CBOREncodableTests.swift b/Tests/CBOREncodableTests.swift index 83f7a0d..e2e3630 100644 --- a/Tests/CBOREncodableTests.swift +++ b/Tests/CBOREncodableTests.swift @@ -1,5 +1,6 @@ import XCTest @testable import SwiftCBOR +import OrderedCollections class CBOREncodableTests: XCTestCase { func testToCBOR() { @@ -58,9 +59,10 @@ class CBOREncodableTests: XCTestCase { XCTAssertEqual(CBOR.array([CBOR.unsignedInt(1), CBOR.unsignedInt(2)]), [1, 2].toCBOR(options: CBOROptions())) + let orderedDict: OrderedDictionary = ["a": 1, "b": 2] XCTAssertEqual( CBOR.map([CBOR.utf8String("a"): CBOR.unsignedInt(1), CBOR.utf8String("b"): CBOR.unsignedInt(2)]), - ["a": 1, "b": 2].toCBOR(options: CBOROptions()) + orderedDict.toCBOR(options: CBOROptions()) ) } } diff --git a/Tests/CBORTests.swift b/Tests/CBORTests.swift index e888f24..fdef8d4 100644 --- a/Tests/CBORTests.swift +++ b/Tests/CBORTests.swift @@ -1,4 +1,5 @@ import XCTest +import OrderedCollections @testable import SwiftCBOR class CBORTests: XCTestCase { @@ -50,7 +51,7 @@ class CBORTests: XCTestCase { let cborEncoded: [UInt8] = try! CBOR.encodeMap(dictionary) var cbor = try! CBOR.decode(cborEncoded)! - let nestedMap: [CBOR: CBOR] = [ + let nestedMap: OrderedDictionary = [ "joe": "schmoe", "age": 56 ]