├── Sources └── DCBOR │ ├── Exports.swift │ ├── CBORTaggedCodable.swift │ ├── CBORCodable.swift │ ├── CBORFloat16.swift │ ├── Bytes.swift │ ├── Text.swift │ ├── DCBOR.docc │ └── DCBOR.md │ ├── Date.swift │ ├── CBORDecodable.swift │ ├── Bool.swift │ ├── CBOREncodable.swift │ ├── CBORTaggedEncodable.swift │ ├── Utils.swift │ ├── Array.swift │ ├── Tagged.swift │ ├── CBORError.swift │ ├── CBORTaggedDecodable.swift │ ├── Simple.swift │ ├── VarInt.swift │ ├── Map.swift │ ├── CBOR.swift │ ├── Dump.swift │ ├── Int.swift │ ├── Float.swift │ ├── Decode.swift │ └── Diag.swift ├── CODEOWNERS ├── SECURITY-REVIEW.md ├── .gitignore ├── Package.swift ├── Package.resolved ├── LICENSE ├── CLA.md ├── CONTRIBUTING.md ├── CLA-signed └── CLA.wolfmcnally.943652EE38441760C3DC35364B6C2FCF894780AE.md ├── README.md └── Tests └── DCBORTests ├── CodingTests.swift └── FormatTests.swift /Sources/DCBOR/Exports.swift: -------------------------------------------------------------------------------- 1 | @_exported import BCFloat16 2 | @_exported import BCTags 3 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in this repo. 2 | 3 | * @ChristopherA 4 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBORTaggedCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | public protocol CBORTaggedCodable: CBORCodable & CBORTaggedEncodable & CBORTaggedDecodable { } 4 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBORCodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A type that can be encoded to or decoded from CBOR. 4 | public protocol CBORCodable: CBOREncodable & CBORDecodable { } 5 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBORFloat16.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BCFloat16 3 | 4 | #if ((os(macOS) || targetEnvironment(macCatalyst)) && arch(x86_64)) 5 | public typealias CBORFloat16 = BCFloat16 6 | #else 7 | public typealias CBORFloat16 = Float16 8 | #endif 9 | -------------------------------------------------------------------------------- /Sources/DCBOR/Bytes.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Data: CBORCodable { 4 | public var cbor: CBOR { 5 | .bytes(self) 6 | } 7 | 8 | public var cborData: Data { 9 | count.encodeVarInt(.bytes) + self 10 | } 11 | 12 | public init(cbor: CBOR) throws { 13 | switch cbor { 14 | case .bytes(let data): 15 | self = data 16 | default: 17 | throw CBORError.wrongType 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Sources/DCBOR/Text.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension String: CBORCodable { 4 | public var cbor: CBOR { 5 | .text(self) 6 | } 7 | 8 | public var cborData: Data { 9 | let data = self.utf8Data 10 | return data.count.encodeVarInt(.text) + data 11 | } 12 | 13 | public init(cbor: CBOR) throws { 14 | switch cbor { 15 | case .text(let string): 16 | self = string 17 | default: 18 | throw CBORError.wrongType 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Sources/DCBOR/DCBOR.docc/DCBOR.md: -------------------------------------------------------------------------------- 1 | # ``DCBOR`` 2 | 3 | Deterministic CBOR ("DCBOR") codec. 4 | 5 | ## Overview 6 | 7 | `DCBOR` is a CBOR codec that focuses on writing and parsing “deterministic” CBOR per §4.2 of RFC-8949. It does not support parts of the spec forbidden by deterministic CBOR (such as indefinite length arrays and maps). It is strict in both what it writes and reads: in particular it will throw decoding errors if variable-length integers are not encoded in their minimal form, or CBOR map keys are not in lexicographic order, or there is extra data past the end of the decoded CBOR item. 8 | -------------------------------------------------------------------------------- /Sources/DCBOR/Date.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Date: CBORTaggedCodable { 4 | // See: https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml 5 | public static let cborTags: [Tag] = [1, 100] 6 | 7 | public var untaggedCBOR: CBOR { 8 | // AR: Ugly fix 9 | if Int64(exactly: timeIntervalSince1970) != nil { 10 | return CBOR(Int64(timeIntervalSince1970)) 11 | } 12 | return CBOR(timeIntervalSince1970) 13 | } 14 | 15 | public init(untaggedCBOR: CBOR) throws { 16 | self = try Date(timeIntervalSince1970: TimeInterval(cbor: untaggedCBOR)) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBORDecodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A type that can be decoded from CBOR 4 | public protocol CBORDecodable { 5 | /// Creates an instance of this type from CBOR symbolic representation. 6 | init(cbor: CBOR) throws 7 | 8 | /// Creates an instance of this type from encoded CBOR binary data. 9 | init(cborData: Data) throws 10 | } 11 | 12 | public extension CBORDecodable { 13 | init(cborData: Data) throws { 14 | self = try Self.init(cbor: CBOR(cborData)) 15 | } 16 | 17 | init?(cbor: CBOR?) throws { 18 | guard let cbor else { 19 | return nil 20 | } 21 | try self.init(cbor: cbor) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Sources/DCBOR/Bool.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Bool: CBORCodable { 4 | public static let cborFalse = CBOR.simple(.false) 5 | public static let cborTrue = CBOR.simple(.true) 6 | public static let cborFalseEncoded = 20.encodeVarInt(.simple) 7 | public static let cborTrueEncoded = 21.encodeVarInt(.simple) 8 | 9 | public var cbor: CBOR { 10 | self ? Self.cborTrue : Self.cborFalse 11 | } 12 | 13 | public var cborData: Data { 14 | self ? Self.cborTrueEncoded : Self.cborFalseEncoded 15 | } 16 | 17 | public init(cbor: CBOR) throws { 18 | switch cbor { 19 | case Self.cborFalse: 20 | self = false 21 | case Self.cborTrue: 22 | self = true 23 | default: 24 | throw CBORError.wrongType 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBOREncodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A type that can be encoded as CBOR. 4 | /// 5 | /// ## Conforming Native Types 6 | /// 7 | /// In addition to types defined in this package like ``Map``, and ``Tagged``, the following 8 | /// native types also conform to ``CBOREncodable``: 9 | /// 10 | /// * `Array where Element: CBOREncodable` 11 | /// * `Bool` 12 | /// * `Data` 13 | /// * `Date` 14 | /// * `Int` 15 | /// * `Int8` 16 | /// * `Int16` 17 | /// * `Int32` 18 | /// * `Int64` 19 | /// * `String` 20 | /// * `UInt` 21 | /// * `UInt8` 22 | /// * `UInt16` 23 | /// * `UInt32` 24 | /// * `UInt64` 25 | public protocol CBOREncodable { 26 | /// Returns the value in CBOR symbolic representation. 27 | var cbor: CBOR { get } 28 | 29 | /// Returns the value in CBOR binary representation. 30 | var cborData: Data { get } 31 | } 32 | 33 | public extension CBOREncodable { 34 | var cborData: Data { 35 | cbor.cborData 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBORTaggedEncodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A type that can be encoded to CBOR with a specific tag. 4 | /// 5 | /// Typically types that conform to this protocol will only provide the 6 | /// `cborTag` static attribute and the `untaggedCBOR` instance attribute. 7 | public protocol CBORTaggedEncodable: CBOREncodable { 8 | static var cborTags: [Tag] { get } 9 | var untaggedCBOR: CBOR { get } 10 | var taggedCBOR: CBOR { get } 11 | 12 | // Overrdiable from CBOREncodable 13 | var cbor: CBOR { get } 14 | var cborData: Data { get } 15 | } 16 | 17 | public extension CBORTaggedEncodable { 18 | var taggedCBOR: CBOR { 19 | CBOR.tagged(Self.cborTags.first!, untaggedCBOR) 20 | } 21 | 22 | /// This override specifies that the default CBOR encoding will be tagged. 23 | var cbor: CBOR { 24 | taggedCBOR 25 | } 26 | 27 | /// This override specifies that the default CBOR encoding will be tagged. 28 | var cborData: Data { 29 | taggedCBOR.cborData 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SECURITY-REVIEW.md: -------------------------------------------------------------------------------- 1 | # Security Review 2 | 3 | This project has not yet received a formal security review. 4 | 5 | If you wish to use this project in a shipping product, we encourage you to work with us to fund a security review. Previous projects such as [`bc-shamir`](https://github.com/BlockchainCommons/bc-shamir/blob/master/SECURITY-REVIEW.md) and [`bc-sskr`](https://github.com/BlockchainCommons/bc-sskr/blob/master/SECURITY-REVIEW.md) have already received security reviews courtesy of existing patrons, such as [Bitmark], allowing you to use those projects with a strong belief in their security properties. By funding a review you can contribute to the set of Blockchain Commons libraries that can be safely used in production projects — a large library of cryptography and interoperability products that you do not need to entirely review yourself. 6 | 7 | Security reviews are done by third parties. Previous reviews have been done by [Radically Open Security](https://radicallyopensecurity.com/). Following a review, our goal is to make the report open for everyone to see. 8 | 9 | To fund a security review, contact us at team@blockchaincommons.com. 10 | -------------------------------------------------------------------------------- /Sources/DCBOR/Utils.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func toHex(byte: UInt8) -> String { 4 | String(format: "%02x", byte) 5 | } 6 | 7 | func toHex(data: Data) -> String { 8 | data.reduce(into: "") { 9 | $0 += toHex(byte: $1) 10 | } 11 | } 12 | 13 | func toUTF8(data: Data) -> String? { 14 | String(data: data, encoding: .utf8) 15 | } 16 | 17 | extension Data { 18 | var hex: String { 19 | toHex(data: self) 20 | } 21 | 22 | var utf8: String? { 23 | toUTF8(data: self) 24 | } 25 | } 26 | 27 | extension Data { 28 | init(of a: A) { 29 | let d = Swift.withUnsafeBytes(of: a) { 30 | Data($0) 31 | } 32 | self = d 33 | } 34 | } 35 | 36 | extension StringProtocol { 37 | func flanked(_ leading: String, _ trailing: String) -> String { 38 | leading + self + trailing 39 | } 40 | 41 | func flanked(_ around: String) -> String { 42 | around + self + around 43 | } 44 | } 45 | 46 | func toData(utf8: String) -> Data { 47 | utf8.data(using: .utf8)! 48 | } 49 | 50 | extension String { 51 | var utf8Data: Data { 52 | toData(utf8: self) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Sources/DCBOR/Array.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Array: CBOREncodable where Element: CBOREncodable { 4 | public var cbor: CBOR { 5 | .array(self.map { $0.cbor }) 6 | } 7 | 8 | public var cborData: Data { 9 | var buf = self.count.encodeVarInt(.array) 10 | for element in self { 11 | buf += element.cborData 12 | } 13 | return buf 14 | } 15 | } 16 | 17 | extension Array: CBORDecodable where Element: CBORDecodable { 18 | public init(cbor: CBOR) throws { 19 | switch cbor { 20 | case .array(let array): 21 | self = try array.map { try Element(cbor: $0) } 22 | default: 23 | throw CBORError.wrongType 24 | } 25 | } 26 | } 27 | 28 | extension Array: CBORCodable where Element: CBORCodable { } 29 | 30 | public extension Array where Element == any CBOREncodable { 31 | var cbor: CBOR { 32 | .array(self.map { $0.cbor }) 33 | } 34 | 35 | var cborData: Data { 36 | var buf = self.count.encodeVarInt(.array) 37 | for element in self { 38 | buf += element.cborData 39 | } 40 | return buf 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Sources/DCBOR/Tagged.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A CBOR tagged value. 4 | public struct Tagged: Equatable { 5 | /// The tag. 6 | public let tag: Tag 7 | /// The CBOR item that was tagged. 8 | public let item: CBOR 9 | 10 | /// Creates a new tagged value. 11 | public init(_ tag: Tag, _ item: T) where T: CBOREncodable { 12 | self.tag = tag 13 | self.item = item.cbor 14 | } 15 | } 16 | 17 | extension Tagged: CBORCodable { 18 | public var cbor: CBOR { 19 | .tagged(tag, item) 20 | } 21 | 22 | public var cborData: Data { 23 | tag.value.encodeVarInt(.tagged) + item.cborData 24 | } 25 | 26 | public init(cbor: CBOR) throws { 27 | switch cbor { 28 | case .tagged(let tag, let item): 29 | self = Tagged(tag, item) 30 | default: 31 | throw CBORError.wrongType 32 | } 33 | } 34 | } 35 | 36 | extension Tagged: CustomDebugStringConvertible { 37 | public var debugDescription: String { 38 | "tagged(tag: \(tag), item: \(item.debugDescription))" 39 | } 40 | } 41 | 42 | extension Tagged: CustomStringConvertible { 43 | public var description: String { 44 | let a = "\(tag)(\(item))" 45 | return a 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/macos 2 | 3 | ### macOS ### 4 | # General 5 | .DS_Store 6 | .AppleDouble 7 | .LSOverride 8 | 9 | # Icon must end with two \r 10 | Icon 11 | 12 | # Thumbnails 13 | ._* 14 | 15 | # Files that might appear in the root of a volume 16 | .DocumentRevisions-V100 17 | .fseventsd 18 | .Spotlight-V100 19 | .TemporaryItems 20 | .Trashes 21 | .VolumeIcon.icns 22 | .com.apple.timemachine.donotpresent 23 | 24 | # Directories potentially created on remote AFP share 25 | .AppleDB 26 | .AppleDesktop 27 | Network Trash Folder 28 | Temporary Items 29 | .apdisk 30 | 31 | # End of https://www.gitignore.io/api/macos 32 | 33 | # Created by https://www.gitignore.io/api/c 34 | 35 | ### C ### 36 | # Prerequisites 37 | *.d 38 | 39 | # Object files 40 | *.o 41 | *.ko 42 | *.obj 43 | *.elf 44 | 45 | # Linker output 46 | *.ilk 47 | *.map 48 | *.exp 49 | 50 | # Precompiled Headers 51 | *.gch 52 | *.pch 53 | 54 | # Libraries 55 | *.lib 56 | *.a 57 | *.la 58 | *.lo 59 | 60 | # Shared objects (inc. Windows DLLs) 61 | *.dll 62 | *.so 63 | *.so.* 64 | *.dylib 65 | 66 | # Executables 67 | *.exe 68 | *.out 69 | *.app 70 | *.i*86 71 | *.x86_64 72 | *.hex 73 | 74 | # Debug files 75 | *.dSYM/ 76 | *.su 77 | *.idb 78 | *.pdb 79 | 80 | # Kernel Module Compile Results 81 | *.mod* 82 | *.cmd 83 | .tmp_versions/ 84 | modules.order 85 | Module.symvers 86 | Mkfile.old 87 | dkms.conf 88 | 89 | # End of https://www.gitignore.io/api/c 90 | 91 | .swiftpm/ 92 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "DCBOR", 7 | platforms: [ 8 | .macOS(.v13), 9 | .iOS(.v14), 10 | .macCatalyst(.v14) 11 | ], 12 | products: [ 13 | .library( 14 | name: "DCBOR", 15 | targets: ["DCBOR"]), 16 | ], 17 | dependencies: [ 18 | .package(url: "https://github.com/wolfmcnally/swift-collections", from: "1.1.0"), 19 | .package(url: "https://github.com/wolfmcnally/WolfBase", from: "6.0.0"), 20 | .package(url: "https://github.com/blockchaincommons/BCSwiftFloat16", from: "1.0.0"), 21 | .package(url: "https://github.com/BlockchainCommons/BCSwiftTags", from: "0.1.0"), 22 | .package(url: "https://github.com/wolfmcnally/swift-numberkit.git", .upToNextMajor(from: "2.4.3")), 23 | ], 24 | targets: [ 25 | .target( 26 | name: "DCBOR", 27 | dependencies: [ 28 | .product(name: "BCFloat16", package: "BCSwiftFloat16"), 29 | .product(name: "Collections", package: "swift-collections"), 30 | .product(name: "BCTags", package: "BCSwiftTags"), 31 | .product(name: "NumberKit", package: "swift-numberkit"), 32 | ]), 33 | .testTarget( 34 | name: "DCBORTests", 35 | dependencies: [ 36 | "DCBOR", 37 | "WolfBase", 38 | ]), 39 | ] 40 | ) 41 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBORError.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// An error decoding or parsing CBOR. 4 | public enum CBORError: LocalizedError, Equatable { 5 | /// Early end of data. 6 | case underrun 7 | 8 | /// Unsupported value in CBOR header. 9 | /// 10 | /// The case includes the encountered header as associated data. 11 | case badHeaderValue(encountered: UInt8) 12 | 13 | /// A numeric value was encoded in non-canonical form. 14 | case nonCanonicalNumeric 15 | 16 | /// An invalid simple value was encountered. 17 | case invalidSimple 18 | 19 | /// An invalidly-encoded UTF-8 string was encountered. 20 | case invalidString 21 | 22 | /// The decoded CBOR had extra data at the end. 23 | /// 24 | /// The case includes the number of unused bytes as associated data. 25 | case unusedData(Int) 26 | 27 | /// The decoded CBOR map has keys that are not in canonical order. 28 | case misorderedMapKey 29 | 30 | /// The decoded CBOR map has a duplicate key. 31 | case duplicateMapKey 32 | 33 | /// The numeric value could not be represented in the specified numeric type. 34 | case outOfRange 35 | 36 | /// The decoded value was not the expected type. 37 | case wrongType 38 | 39 | /// The decoded value did not have the expected tag. 40 | /// 41 | /// The case includes the expected tag and encountered tag as associated data. 42 | case wrongTag(expected: Tag, encountered: Tag) 43 | 44 | /// Invalid CBOR format. Frequently thrown by libraries depending on this one. 45 | case invalidFormat 46 | } 47 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBORTaggedDecodable.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A type that can be decoded from CBOR with a specific tag. 4 | /// 5 | /// Typically types that conform to this protocol will provide the `cborTag` 6 | /// static attribute and the `init(untaggedCBOR:)` constructor. 7 | public protocol CBORTaggedDecodable: CBORDecodable { 8 | /// The CBOR tags assocated with this type. If more than one tag is present, 9 | /// they are considered equivalent for reading, but only the first one is 10 | /// used for writing. 11 | static var cborTags: [Tag] { get } 12 | init(untaggedCBOR: CBOR) throws 13 | 14 | // Overridable from CBORDecodable 15 | init(cbor: CBOR) throws 16 | } 17 | 18 | public extension CBORTaggedDecodable { 19 | static var cborTag: Tag { 20 | cborTags.first! 21 | } 22 | 23 | /// Creates an instance of this type by decoding it from tagged CBOR. 24 | init(taggedCBOR: CBOR) throws { 25 | guard case CBOR.tagged(let tag, let item) = taggedCBOR else { 26 | throw CBORError.wrongType 27 | } 28 | let tags = Self.cborTags 29 | guard tags.contains(tag) else { 30 | throw CBORError.wrongTag(expected: tags.first!, encountered: tag) 31 | } 32 | self = try Self(untaggedCBOR: item) 33 | } 34 | 35 | /// Creates an instance of this type by decoding it from binary encoded tagged CBOR. 36 | init(taggedCBORData: Data) throws { 37 | self = try Self(taggedCBOR: CBOR(taggedCBORData)) 38 | } 39 | 40 | /// Creates an instance of this type by decoding it from binary encoded untagged CBOR. 41 | init(untaggedCBORData: Data) throws { 42 | self = try Self(untaggedCBOR: CBOR(untaggedCBORData)) 43 | } 44 | 45 | /// This override specifies that default CBOR encoding will be tagged. 46 | init(cbor: CBOR) throws { 47 | self = try Self(taggedCBOR: cbor) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "bcswiftfloat16", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/blockchaincommons/BCSwiftFloat16.git", 7 | "state" : { 8 | "revision" : "71f380285d88a876f9cdf4e34e2e9e77d7ca4aef", 9 | "version" : "1.0.0" 10 | } 11 | }, 12 | { 13 | "identity" : "bcswifttags", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/BlockchainCommons/BCSwiftTags", 16 | "state" : { 17 | "revision" : "a2f3e5c8e8ed1770fb3cb28b35aef252da7a0fc2", 18 | "version" : "0.1.1" 19 | } 20 | }, 21 | { 22 | "identity" : "swift-algorithms", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/apple/swift-algorithms.git", 25 | "state" : { 26 | "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42", 27 | "version" : "1.2.0" 28 | } 29 | }, 30 | { 31 | "identity" : "swift-collections", 32 | "kind" : "remoteSourceControl", 33 | "location" : "https://github.com/wolfmcnally/swift-collections", 34 | "state" : { 35 | "revision" : "53a8adc54374f620002a3b6401d39e0feb3c57ae", 36 | "version" : "1.1.0" 37 | } 38 | }, 39 | { 40 | "identity" : "swift-numberkit", 41 | "kind" : "remoteSourceControl", 42 | "location" : "https://github.com/wolfmcnally/swift-numberkit.git", 43 | "state" : { 44 | "revision" : "34a26297c200489779929b7fd7d4d30b63e87e69", 45 | "version" : "2.4.3" 46 | } 47 | }, 48 | { 49 | "identity" : "swift-numerics", 50 | "kind" : "remoteSourceControl", 51 | "location" : "https://github.com/apple/swift-numerics", 52 | "state" : { 53 | "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", 54 | "version" : "1.0.2" 55 | } 56 | }, 57 | { 58 | "identity" : "wolfbase", 59 | "kind" : "remoteSourceControl", 60 | "location" : "https://github.com/wolfmcnally/WolfBase.git", 61 | "state" : { 62 | "revision" : "91c8cad45e6325ab76db1b5b60c5f6616d52a0cb", 63 | "version" : "6.0.0" 64 | } 65 | } 66 | ], 67 | "version" : 2 68 | } 69 | -------------------------------------------------------------------------------- /Sources/DCBOR/Simple.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | /// A CBOR simple value. 4 | public enum Simple: Equatable { 5 | /// A numeric value 6 | //case value(UInt64) 7 | /// The boolean value `false`. 8 | case `false` 9 | /// The boolean value `true`. 10 | case `true` 11 | /// The value representing `null` (`nil`). 12 | case null 13 | /// A floating point value. 14 | case float(Double) 15 | } 16 | 17 | public extension Simple { 18 | /// Creates a CBOR simple value from the given numeric value. 19 | //init(_ v: UInt64) { 20 | // self = .value(v) 21 | //} 22 | } 23 | 24 | extension Simple: CBORCodable { 25 | public var cbor: CBOR { 26 | .simple(self) 27 | } 28 | 29 | public var cborData: Data { 30 | switch self { 31 | //case .value(let v): 32 | // return v.encodeVarInt(.simple) 33 | case .false: 34 | return 20.encodeVarInt(.simple) 35 | case .true: 36 | return 21.encodeVarInt(.simple) 37 | case .null: 38 | return 22.encodeVarInt(.simple) 39 | case .float(let n): 40 | return n.cborData 41 | } 42 | } 43 | 44 | public init(cbor: CBOR) throws { 45 | switch cbor { 46 | case .simple(let s): 47 | self = s 48 | default: 49 | throw CBORError.wrongType 50 | } 51 | } 52 | } 53 | 54 | extension Simple: CustomDebugStringConvertible { 55 | public var debugDescription: String { 56 | switch self { 57 | //case .value(let v): 58 | // return String(v) 59 | case .false: 60 | return "false" 61 | case .true: 62 | return "true" 63 | case .null: 64 | return "null" 65 | case .float(let n): 66 | return String(n) 67 | } 68 | } 69 | } 70 | 71 | extension Simple: CustomStringConvertible { 72 | public var description: String { 73 | switch self { 74 | //case .value(let v): 75 | // return "simple(\(v))" 76 | case .false: 77 | return "false" 78 | case .true: 79 | return "true" 80 | case .null: 81 | return "null" 82 | case .float(let n): 83 | return String(n) 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Unless otherwise noted (either in /README.md or in the file's header comments) the contents of this repository are released under the following license: 2 | 3 | BSD-2-Clause Plus Patent License 4 | 5 | SPDX-License-Identifier: [BSD-2-Clause-Patent](https://spdx.org/licenses/BSD-2-Clause-Patent.html) 6 | 7 | Copyright © 2019 Blockchain Commons, LLC 8 | 9 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 10 | 11 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 13 | Subject to the terms and conditions of this license, each copyright holder and contributor hereby grants to those receiving rights under this license a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except for failure to satisfy the conditions of this license) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer this software, where such license applies only to those patent claims, already acquired or hereafter acquired, licensable by such copyright holder or contributor that are necessarily infringed by: 14 | 15 | (a) their Contribution(s) (the licensed copyrights of copyright holders and non-copyrightable additions of contributors, in source or binary form) alone; or 16 | (b) combination of their Contribution(s) with the work of authorship to which such Contribution(s) was added by such copyright holder or contributor, if, at the time the Contribution is added, such addition causes such combination to be necessarily infringed. The patent license shall not apply to any other combinations which include the Contribution. 17 | Except as expressly stated above, no rights or licenses from any copyright holder or contributor is granted under this license, whether expressly, by implication, estoppel or otherwise. 18 | 19 | DISCLAIMER 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 22 | -------------------------------------------------------------------------------- /Sources/DCBOR/VarInt.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | enum MajorType: Int { 4 | case unsigned 5 | case negative 6 | case bytes 7 | case text 8 | case array 9 | case map 10 | case tagged 11 | case simple 12 | } 13 | 14 | func typeBits(_ t: MajorType) -> UInt8 { 15 | UInt8(t.rawValue << 5) 16 | } 17 | 18 | protocol EncodeVarInt { 19 | func encodeVarInt(_ majorType: MajorType) -> Data 20 | func encodeInt(_ majorType: MajorType) -> Data 21 | } 22 | 23 | extension UInt8: EncodeVarInt { 24 | func encodeVarInt(_ majorType: MajorType) -> Data { 25 | if self <= 23 { 26 | return Data([self | typeBits(majorType)]) 27 | } else { 28 | return encodeInt(majorType) 29 | } 30 | } 31 | 32 | func encodeInt(_ majorType: MajorType) -> Data { 33 | return Data([ 34 | 0x18 | typeBits(majorType), 35 | self 36 | ]) 37 | } 38 | } 39 | 40 | extension UInt16: EncodeVarInt { 41 | func encodeVarInt(_ majorType: MajorType) -> Data { 42 | if self <= UInt8.max { 43 | return UInt8(self).encodeVarInt(majorType) 44 | } else { 45 | return encodeInt(majorType) 46 | } 47 | } 48 | 49 | func encodeInt(_ majorType: MajorType) -> Data { 50 | return Data([ 51 | 0x19 | typeBits(majorType), 52 | UInt8(truncatingIfNeeded: self >> 8), UInt8(truncatingIfNeeded: self) 53 | ]) 54 | } 55 | } 56 | 57 | extension UInt32: EncodeVarInt { 58 | func encodeVarInt(_ majorType: MajorType) -> Data { 59 | if self <= UInt16.max { 60 | return UInt16(self).encodeVarInt(majorType) 61 | } else { 62 | return encodeInt(majorType) 63 | } 64 | } 65 | 66 | func encodeInt(_ majorType: MajorType) -> Data { 67 | return Data([ 68 | 0x1a | typeBits(majorType), 69 | UInt8(truncatingIfNeeded: self >> 24), UInt8(truncatingIfNeeded: self >> 16), 70 | UInt8(truncatingIfNeeded: self >> 8), UInt8(truncatingIfNeeded: self) 71 | ]) 72 | } 73 | } 74 | 75 | extension UInt64: EncodeVarInt { 76 | func encodeVarInt(_ majorType: MajorType) -> Data { 77 | if self <= UInt32.max { 78 | return UInt32(self).encodeVarInt(majorType) 79 | } else { 80 | return encodeInt(majorType) 81 | } 82 | } 83 | 84 | func encodeInt(_ majorType: MajorType) -> Data { 85 | return Data([ 86 | 0x1b | typeBits(majorType), 87 | UInt8(truncatingIfNeeded: self >> 56), UInt8(truncatingIfNeeded: self >> 48), 88 | UInt8(truncatingIfNeeded: self >> 40), UInt8(truncatingIfNeeded: self >> 32), 89 | UInt8(truncatingIfNeeded: self >> 24), UInt8(truncatingIfNeeded: self >> 16), 90 | UInt8(truncatingIfNeeded: self >> 8), UInt8(truncatingIfNeeded: self) 91 | ]) 92 | } 93 | } 94 | 95 | extension UInt: EncodeVarInt { 96 | func encodeVarInt(_ majorType: MajorType) -> Data { 97 | UInt64(self).encodeVarInt(majorType) 98 | } 99 | 100 | func encodeInt(_ majorType: MajorType) -> Data { 101 | UInt64(self).encodeInt(majorType) 102 | } 103 | } 104 | 105 | 106 | extension Int: EncodeVarInt { 107 | func encodeVarInt(_ majorType: MajorType) -> Data { 108 | UInt64(self).encodeVarInt(majorType) 109 | } 110 | 111 | func encodeInt(_ majorType: MajorType) -> Data { 112 | UInt64(self).encodeInt(majorType) 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /CLA.md: -------------------------------------------------------------------------------- 1 | # Contributor License Agreement 2 | 3 | Version 1.0 4 | 5 | Name: `$name` 6 | 7 | E-Mail: `$email` 8 | 9 | Legal Jurisdiction: Wyoming, United States of America 10 | 11 | Project: https://github.com/BlockchainCommons/BCSwiftDCBOR 12 | 13 | Date: `$date` 14 | 15 | ## Purpose 16 | 17 | This agreement gives Blockchain Commons, LLC the permission it needs in order to accept my contributions into its open software project and to manage the intellectual property in that project over time. 18 | 19 | ## License 20 | 21 | I hereby license Blockchain Commons, LLC to: 22 | 23 | 1. do anything with my contributions that would otherwise infringe my copyright in them 24 | 25 | 2. do anything with my contributions that would otherwise infringe patents that I can or become able to license 26 | 27 | 3. sublicense these rights to others on any terms they like 28 | 29 | ## Reliability 30 | 31 | I understand that Blockchain Commons will rely on this license. I may not revoke this license. 32 | 33 | ## Awareness 34 | 35 | I promise that I am familiar with legal rules, like ["work made for hire" rules](http://worksmadeforhire.com), that can give employers and clients ownership of intellectual property in work that I do. I am also aware that legal agreements I might sign, like confidential information and invention assignment agreements, will usually give ownership of intellectual property in my work to employers, clients, and companies that I found. If someone else owns intellectual property in my work, I need their permission to license it. 36 | 37 | ## Copyright Guarantee 38 | 39 | I promise not to offer contributions to the project that contain copyrighted work that I do not have legally binding permission to contribute under these terms. When I offer a contribution with permission, I promise to document in the contribution who owns copyright in what work, and how they gave permission to contribute it. If I later become aware that one of my contributions may have copyrighted work of others that I did not have permission to contribute, I will notify Blockchain Commons, in confidence, immediately. 40 | 41 | ## Patent Guarantee 42 | 43 | I promise not to offer contributions to the project that I know infringe patents of others that I do not have permission to contribute under these terms. 44 | 45 | ## Open Source Guarantee 46 | 47 | I promise not to offer contributions that contain or depend on the work of others, unless that work is available under a license that [Blue Oak Council rates bronze or better](https://blueoakconcil.org/list), such as the MIT License, two- or three-clause BSD License, the Apache License Version 2.0, or the Blue Oak Model License 1.0.0. When I offer a contribution containing or depending on others' work, I promise to document in the contribution who licenses that work, along with copies of their license terms. 48 | 49 | ## Disclaimers 50 | 51 | ***As far as the law allows, my contributions come as is, without any warranty or condition. Other than under [Copyright Guarantee](#copyright-guarantee), [Patent Guarantee](#patent-guarantee), or [Open Source Guarantee](#open-source-guarantee), I will not be liable to anyone for any damages related to my contributions or this contributor license agreement, under any kind of legal claim.*** 52 | 53 | --- 54 | 55 | To sign this Contributor License Agreement, fill in `$name`, `$email`, and `$date` above. Then sign using GPG using the following command `gpg --armor --clearsign --output ./CLA-signed/CLA.YOURGITHUBNAME.YOURGPGFINGERPRINT.asc CLA.md`, then either submit your signed Contributor License Agreement to this repo as a GPG signed Pull Request or email it to [ChristopherA@BlockchainCommons.com](mailto:ChristopherA@BlockchainCommons.com). 56 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We love your input! We want to make contributing to this project as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## We Develop with Github 12 | We use GitHub to host code, to track issues and feature requests, and to accept Pull Requests. 13 | 14 | ## Report Bugs using Github's [issues](https://github.com/briandk/transcriptase-atom/issues) 15 | 16 | If you find bugs, mistakes, or inconsistencies in this project's code or documents, please let us know by [opening a new issue](./issues), but consider searching through existing issues first to check and see if the problem has already been reported. If it has, it never hurts to add a quick "+1" or "I have this problem too". This helps prioritize the most common problems and requests. 17 | 18 | ### Write Bug Reports with Detail, Background, and Sample Code 19 | 20 | [This is an example](http://stackoverflow.com/q/12488905/180626) of a good bug report by @briandk. Here's [another example from craig.hockenberry](http://www.openradar.me/11905408). 21 | 22 | **Great Bug Reports** tend to have: 23 | 24 | - A quick summary and/or background 25 | - Steps to reproduce 26 | - Be specific! 27 | - Give sample code if you can. [The stackoverflow bug report](http://stackoverflow.com/q/12488905/180626) includes sample code that *anyone* with a base R setup can run to reproduce what I was seeing 28 | - What you expected would happen 29 | - What actually happens 30 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 31 | 32 | People *love* thorough bug reports. I'm not even kidding. 33 | 34 | ## Submit Code Changes through Pull Requests 35 | 36 | Simple Pull Requests to fix typos, to document, or to fix small bugs are always welcome. 37 | 38 | We ask that more significant improvements to the project be first proposed before anybody starts to code as an [issue](./issues) or as a [draft Pull Request](./pulls), which is a [nice new feature](https://github.blog/2019-02-14-introducing-draft-pull-requests/) that gives other contributors a chance to point you in the right direction, give feedback on the design, and maybe discuss if related work is already under way. 39 | 40 | ### Use a Consistent Coding Style 41 | 42 | * We indent using two spaces (soft tabs) 43 | * We ALWAYS put spaces after list items and method parameters ([1, 2, 3], not [1,2,3]), around operators (x += 1, not x+=1), and around hash arrows. 44 | * This is open-source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth as possible. 45 | 46 | ### Use [Github Flow](https://guides.github.com/introduction/flow/index.html) for Pull Requests 47 | 48 | We use [Github Flow](https://guides.github.com/introduction/flow/index.html). When you submit Pull Requests, please: 49 | 50 | 1. Fork the repo and create your branch from `master`. 51 | 2. If you've added code that should be tested, add tests. 52 | 3. If you've changed APIs, update the documentation. 53 | 4. Ensure the test suite passes. 54 | 5. Make sure your code lints. 55 | 6. Issue that Pull Request! 56 | 57 | ### Submit Under the BSD-2-Clause Plus Patent License 58 | 59 | In short, when you submit code changes, your submissions are understood to be available under the same [BSD-2-Clause Plus Patent License](./LICENSE.md) that covers the project. We also ask all code contributors to GPG sign the [Contributor License Agreement (CLA.md)](./CLA.md) to protect future users of this project. Feel free to contact the maintainers if that's a concern. 60 | 61 | ## References 62 | 63 | Portions of this CONTRIBUTING.md document were adopted from best practices of a number of open source projects, including: 64 | * [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) 65 | * [IPFS Contributing](https://github.com/ipfs/community/blob/master/CONTRIBUTING.md) 66 | -------------------------------------------------------------------------------- /CLA-signed/CLA.wolfmcnally.943652EE38441760C3DC35364B6C2FCF894780AE.md: -------------------------------------------------------------------------------- 1 | -----BEGIN PGP SIGNED MESSAGE----- 2 | Hash: SHA256 3 | 4 | # Contributor License Agreement 5 | 6 | Version 1.0 7 | 8 | Name: `Wolf McNally` 9 | 10 | E-Mail: `wolf@wolfmcnally.com` 11 | 12 | Legal Jurisdiction: Wyoming, United States of America 13 | 14 | Project: https://github.com/BlockchainCommons/BCSwiftDCBOR 15 | 16 | Date: `26 January 2023` 17 | 18 | ## Purpose 19 | 20 | This agreement gives Blockchain Commons, LLC the permission it needs in order to accept my contributions into its open software project and to manage the intellectual property in that project over time. 21 | 22 | ## License 23 | 24 | I hereby license Blockchain Commons, LLC to: 25 | 26 | 1. do anything with my contributions that would otherwise infringe my copyright in them 27 | 28 | 2. do anything with my contributions that would otherwise infringe patents that I can or become able to license 29 | 30 | 3. sublicense these rights to others on any terms they like 31 | 32 | ## Reliability 33 | 34 | I understand that Blockchain Commons will rely on this license. I may not revoke this license. 35 | 36 | ## Awareness 37 | 38 | I promise that I am familiar with legal rules, like ["work made for hire" rules](http://worksmadeforhire.com), that can give employers and clients ownership of intellectual property in work that I do. I am also aware that legal agreements I might sign, like confidential information and invention assignment agreements, will usually give ownership of intellectual property in my work to employers, clients, and companies that I found. If someone else owns intellectual property in my work, I need their permission to license it. 39 | 40 | ## Copyright Guarantee 41 | 42 | I promise not to offer contributions to the project that contain copyrighted work that I do not have legally binding permission to contribute under these terms. When I offer a contribution with permission, I promise to document in the contribution who owns copyright in what work, and how they gave permission to contribute it. If I later become aware that one of my contributions may have copyrighted work of others that I did not have permission to contribute, I will notify Blockchain Commons, in confidence, immediately. 43 | 44 | ## Patent Guarantee 45 | 46 | I promise not to offer contributions to the project that I know infringe patents of others that I do not have permission to contribute under these terms. 47 | 48 | ## Open Source Guarantee 49 | 50 | I promise not to offer contributions that contain or depend on the work of others, unless that work is available under a license that [Blue Oak Council rates bronze or better](https://blueoakconcil.org/list), such as the MIT License, two- or three-clause BSD License, the Apache License Version 2.0, or the Blue Oak Model License 1.0.0. When I offer a contribution containing or depending on others' work, I promise to document in the contribution who licenses that work, along with copies of their license terms. 51 | 52 | ## Disclaimers 53 | 54 | ***As far as the law allows, my contributions come as is, without any warranty or condition. Other than under [Copyright Guarantee](#copyright-guarantee), [Patent Guarantee](#patent-guarantee), or [Open Source Guarantee](#open-source-guarantee), I will not be liable to anyone for any damages related to my contributions or this contributor license agreement, under any kind of legal claim.*** 55 | -----BEGIN PGP SIGNATURE----- 56 | 57 | iQIzBAEBCAAdFiEElDZS7jhEF2DD3DU2S2wvz4lHgK4FAmPSOhYACgkQS2wvz4lH 58 | gK7l0hAAl5aJJldk1Kd3ytjnyRfNO8iMSHk50d2t7GNiBseycTDGoGAjCYtmDzxi 59 | PMeP8WHuB+0gaglhJS+fDk/RmeR6uNdM9NdUoCCoaKgHjzJvqeXnomBZUQ55BsbJ 60 | XPsXQHAHcNrVdkqg6fVJMRMLm1FSZ3ysjdsJSSY7o0dDsZkOO12IWTeexoIY8pTC 61 | Whkg6jE3lThA3phjR7LU35YKlT7LQyFFhkfyJMd0XAe7SBaMTRwb1OXLVeFlplyd 62 | 7spFzsY1cwwlGvUDnKs1qKpgAExVEhEN4O7jcRbUyAlnM7X1NV9PChA5Ptzn4t3B 63 | ImRpd96By6KU9F5uICG5Z5CzcdAx43iSaW4u2+iBgKtba8ipi8H1+jWb8n9ad7Xv 64 | biJWBRc43hN3taay4mIgua/3Ems22JHmQqiht98OfLLV2RC3z/XSAfnsCatXRXZq 65 | vFNXZiX/EmY/HE6u7fYDZvXVek1AR84nFoaXNtvFH0Ut4PAInhsV6ZSBR1gF0Jer 66 | qY3EhQJiHYrMVcSUgJinmAdyimveecFTRPgw4tEt5heNAJHbIowg+5L8gd7JS6fW 67 | kC5dlzAgQqFIdEDm0ZRPgUOv5mhIMmW0X7UyB/L02OmYAIKV7jXFoXkXy6D4z7+R 68 | bWPXR/KGQ4TBg0KbmXAQhFbl2KXEjmRl0cp7SOiuLZpJomE/mik= 69 | =hKJM 70 | -----END PGP SIGNATURE----- 71 | -------------------------------------------------------------------------------- /Sources/DCBOR/Map.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import SortedCollections 3 | 4 | /// A CBOR map. 5 | /// 6 | /// Keys are kept sorted by encoded CBOR form in ascending lexicographic order. 7 | public struct Map: Equatable { 8 | var dict: SortedDictionary 9 | 10 | /// Creates a new empty CBOR map. 11 | public init() { 12 | self.dict = .init() 13 | } 14 | 15 | /// Creates a new CBOR map from the provided sequence of key-value pairs. 16 | public init(_ elements: S) where S: Sequence, S.Element == (any CBOREncodable, any CBOREncodable) { 17 | self.init() 18 | for (k, v) in elements { 19 | self.insert(k, v) 20 | } 21 | } 22 | 23 | /// Inserts a key-value pair into the map. 24 | public mutating func insert(_ key: K, _ value: V) where K: CBOREncodable, V: CBOREncodable { 25 | dict[MapKey(key)] = MapValue(key: key.cbor, value: value.cbor) 26 | } 27 | 28 | /// Removes the specified key from the map, returning the removed value if any. 29 | public mutating func remove(_ key: K) -> CBOR? where K: CBOREncodable { 30 | dict.removeValue(forKey: MapKey(key))?.value 31 | } 32 | 33 | /// Returns the number of entries in the map. 34 | public var count: Int { dict.count } 35 | 36 | /// Returns an array of all the key-value pairs in the map. 37 | public var entries: [(key: CBOR, value: CBOR)] { 38 | dict.map { ($1.key, $1.value) } 39 | } 40 | 41 | mutating func insertNext(_ key: K, _ value: V) throws where K: CBOREncodable, V: CBOREncodable { 42 | guard let lastEntry = dict.last else { 43 | self.insert(key, value) 44 | return 45 | } 46 | let newKey = MapKey(key) 47 | guard dict[newKey] == nil else { 48 | throw CBORError.duplicateMapKey 49 | } 50 | let entryKey = lastEntry.key 51 | guard entryKey < newKey else { 52 | throw CBORError.misorderedMapKey 53 | } 54 | self.dict[newKey] = MapValue(key: key.cbor, value: value.cbor) 55 | } 56 | 57 | struct MapValue: Equatable { 58 | let key: CBOR 59 | let value: CBOR 60 | } 61 | 62 | struct MapKey: Comparable { 63 | let keyData: Data 64 | 65 | init(_ keyData: Data) { 66 | self.keyData = keyData 67 | } 68 | 69 | init(_ k: T) where T: CBOREncodable { 70 | self.init(k.cborData) 71 | } 72 | 73 | static func < (lhs: MapKey, rhs: MapKey) -> Bool { 74 | lhs.keyData.lexicographicallyPrecedes(rhs.keyData) 75 | } 76 | } 77 | 78 | public func get(_ key: K) -> CBOR? where K: CBOREncodable { 79 | dict[MapKey(key)]?.value 80 | } 81 | 82 | /// Gets or sets the value for the given key. 83 | public subscript(key: K) -> V? where K: CBOREncodable, V: CBORCodable { 84 | get { 85 | try? V(cbor: dict[MapKey(key)]?.value) 86 | } 87 | 88 | set { 89 | if let newValue { 90 | insert(key, newValue) 91 | } else { 92 | _ = remove(key) 93 | } 94 | } 95 | } 96 | } 97 | 98 | extension Map: ExpressibleByDictionaryLiteral { 99 | public init(dictionaryLiteral elements: (CBOREncodable, CBOREncodable)...) { 100 | self.init(elements) 101 | } 102 | } 103 | 104 | extension Map: Sequence { 105 | public func makeIterator() -> Iterator { 106 | return Iterator(dict) 107 | } 108 | 109 | public struct Iterator: IteratorProtocol { 110 | public typealias Element = (CBOR, CBOR) 111 | var iter: SortedDictionary.Iterator 112 | 113 | init(_ dict: SortedDictionary) { 114 | self.iter = dict.makeIterator() 115 | } 116 | 117 | public mutating func next() -> (CBOR, CBOR)? { 118 | guard let v = iter.next() else { 119 | return nil 120 | } 121 | return (v.value.key, v.value.value) 122 | } 123 | } 124 | } 125 | 126 | extension Map.MapKey: CustomDebugStringConvertible { 127 | var debugDescription: String { 128 | "0x" + keyData.hex 129 | } 130 | } 131 | 132 | extension Map: CBORCodable { 133 | public var cbor: CBOR { 134 | .map(self) 135 | } 136 | 137 | public var cborData: Data { 138 | let pairs = self.dict.map { (key: MapKey, value: MapValue) in 139 | (key, value.value.cborData) 140 | } 141 | var buf = pairs.count.encodeVarInt(.map) 142 | for pair in pairs { 143 | buf += pair.0.keyData 144 | buf += pair.1 145 | } 146 | return buf 147 | } 148 | 149 | public init(cbor: CBOR) throws { 150 | switch cbor { 151 | case .map(let map): 152 | self = map 153 | default: 154 | throw CBORError.wrongType 155 | } 156 | } 157 | } 158 | 159 | extension Map: CustomDebugStringConvertible { 160 | public var debugDescription: String { 161 | dict.map { (k, v) in 162 | "\(k.debugDescription): (\(v.key.debugDescription), \(v.value.debugDescription))" 163 | }.joined(separator: ", ") 164 | .flanked("{", "}") 165 | } 166 | } 167 | 168 | extension Map: CustomStringConvertible { 169 | public var description: String { 170 | dict.map { (k, v) in 171 | "\(v.key): \(v.value)" 172 | }.joined(separator: ", ") 173 | .flanked("{", "}") 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Sources/DCBOR/CBOR.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import NumberKit 3 | 4 | /// A symbolic representation of CBOR data. 5 | public indirect enum CBOR { 6 | /// Unsigned integer (major type 0). 7 | case unsigned(UInt64) 8 | /// Negative integer (major type 1). 9 | /// 10 | /// value = (-n) - 1 11 | case negative(UInt64) 12 | /// Byte string (major type 2). 13 | case bytes(Data) 14 | /// UTF-8 string (major type 3). 15 | case text(String) 16 | /// Array (major type 4). 17 | case array([CBOR]) 18 | /// Map (major type 5). 19 | case map(Map) 20 | /// Tagged value (major type 6). 21 | case tagged(Tag, CBOR) 22 | /// Simple value (major type 7). 23 | case simple(Simple) 24 | } 25 | 26 | public extension CBOR { 27 | /// The CBOR symbolic value for `false`. 28 | static let `false` = Bool.cborFalse 29 | /// The CBOR symbolic value for `true`. 30 | static let `true` = Bool.cborTrue 31 | /// The CBOR symbolic value for `null` (`nil`). 32 | static var null = Simple.null.cbor 33 | 34 | /// Creates the symbolic CBOR representation of a value conforming to ``CBOREncodable``. 35 | init(_ x: T) where T: CBOREncodable { 36 | self = x.cbor 37 | } 38 | } 39 | 40 | extension CBOR: CBOREncodable { 41 | public var cbor: CBOR { 42 | self 43 | } 44 | 45 | public var cborData: Data { 46 | switch self { 47 | case .unsigned(let x): 48 | return x.cborData 49 | case .negative(let x): 50 | return x.encodeVarInt(.negative) 51 | case .bytes(let x): 52 | return x.cborData 53 | case .text(let x): 54 | return x.cborData 55 | case .array(let x): 56 | return x.cborData 57 | case .map(let x): 58 | return x.cborData 59 | case .tagged(let tag, let item): 60 | return tag.value.encodeVarInt(.tagged) + item.cborData 61 | case .simple(let x): 62 | return x.cborData 63 | } 64 | } 65 | } 66 | 67 | extension CBOR: CBORDecodable { 68 | public init(cbor: CBOR) throws { 69 | self = cbor 70 | } 71 | } 72 | 73 | extension CBOR: CBORCodable { } 74 | 75 | extension CBOR: Equatable { 76 | public static func ==(lhs: CBOR, rhs: CBOR) -> Bool { 77 | switch (lhs, rhs) { 78 | case (CBOR.unsigned(let l), CBOR.unsigned(let r)): return l == r 79 | case (CBOR.negative(let l), CBOR.negative(let r)): return l == r 80 | case (CBOR.bytes(let l), CBOR.bytes(let r)): return l == r 81 | case (CBOR.text(let l), CBOR.text(let r)): return l == r 82 | case (CBOR.array(let l), CBOR.array(let r)): return l == r 83 | case (CBOR.map(let l), CBOR.map(let r)): return l == r 84 | case (CBOR.tagged(let ltag, let litem), CBOR.tagged(let rtag, let ritem)): return ltag == rtag && litem == ritem 85 | case (CBOR.simple(let l), CBOR.simple(let r)): return l == r 86 | default: return false 87 | } 88 | } 89 | } 90 | 91 | extension CBOR: CustomStringConvertible { 92 | public var description: String { 93 | switch self { 94 | case .unsigned(let x): 95 | return x.description 96 | case .negative(let x): 97 | return (-BigInt(x) - 1).description 98 | case .bytes(let x): 99 | return x.hex.flanked("h'", "'") 100 | case .text(let x): 101 | return x.description.flanked("\"") 102 | case .array(let x): 103 | return x.map({ $0.description }).joined(separator: ", ").flanked("[", "]") 104 | case .map(let x): 105 | return x.description 106 | case .tagged(let tag, let item): 107 | return "\(tag)(\(item))" 108 | case .simple(let x): 109 | return x.description 110 | } 111 | } 112 | } 113 | 114 | extension CBOR: CustomDebugStringConvertible { 115 | public var debugDescription: String { 116 | switch self { 117 | case .unsigned(let x): 118 | return "unsigned(\(x))" 119 | case .negative(let x): 120 | return "negative(\(-1 - BigInt(x)))" 121 | case .bytes(let x): 122 | return "bytes(\(x.hex))" 123 | case .text(let x): 124 | return "text(\(x.flanked("\"")))" 125 | case .array(let x): 126 | return "array(\(x))" 127 | case .map(let x): 128 | return "map(\(x.debugDescription))" 129 | case .tagged(let tag, let item): 130 | return "tagged(\(tag), \(item.debugDescription))" 131 | case .simple(let x): 132 | return "simple(\(x.debugDescription))" 133 | } 134 | } 135 | } 136 | 137 | public extension CBOR { 138 | /// Decode CBOR binary representation to symbolic representation. 139 | /// 140 | /// Throws an error if the data is not well-formed deterministic CBOR. 141 | init(_ data: Data) throws { 142 | self = try decodeCBOR(data) 143 | } 144 | } 145 | 146 | extension CBOR: ExpressibleByBooleanLiteral { 147 | public init(booleanLiteral value: BooleanLiteralType) { 148 | self = value ? Self.true : Self.false 149 | } 150 | } 151 | 152 | extension CBOR: ExpressibleByNilLiteral { 153 | public init(nilLiteral: ()) { 154 | self = CBOR.null 155 | } 156 | } 157 | 158 | extension CBOR: ExpressibleByIntegerLiteral { 159 | public init(integerLiteral value: IntegerLiteralType) { 160 | self = value.cbor 161 | } 162 | } 163 | 164 | extension CBOR: ExpressibleByStringLiteral { 165 | public init(stringLiteral value: StringLiteralType) { 166 | self = value.cbor 167 | } 168 | } 169 | 170 | extension CBOR: ExpressibleByArrayLiteral { 171 | public init(arrayLiteral elements: CBOREncodable...) { 172 | self = (elements.map { $0.cbor }).cbor 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /Sources/DCBOR/Dump.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import NumberKit 3 | 4 | public extension CBOR { 5 | /// Returns the encoded hexadecimal representation of this CBOR. 6 | var hex: String { 7 | cborData.hex 8 | } 9 | 10 | /// Returns the encoded hexadecimal representation of this CBOR. 11 | /// 12 | /// - Parameters: 13 | /// - annotate: If `true`, add additional notes and context, otherwise just return a 14 | /// straight hexadecimal encoding. 15 | /// - knownTags: If `annotate` is `true`, uses the name of these tags rather than their number. 16 | func hex(annotate: Bool = true, tags: TagsStoreProtocol? = nil) -> String { 17 | guard annotate else { 18 | return cborData.hex 19 | } 20 | let items = dumpItems(level: 0, knownTags: tags) 21 | let noteColumn = items.reduce(into: 0) { largest, item in 22 | largest = max(largest, item.formatFirstColumn().count) 23 | } 24 | let lines = items.map { $0.format(noteColumn: noteColumn) } 25 | return lines.joined(separator: "\n") 26 | } 27 | } 28 | 29 | struct DumpItem { 30 | let level: Int 31 | let data: [Data] 32 | let note: String? 33 | 34 | init(level: Int, data: [Data], note: String? = nil) { 35 | self.level = level 36 | self.data = data 37 | self.note = note 38 | } 39 | 40 | init(level: Int, data: Data, note: String? = nil) { 41 | self.init(level: level, data: [data], note: note) 42 | } 43 | 44 | func format(noteColumn: Int) -> String { 45 | let column1 = formatFirstColumn() 46 | let column2: String 47 | let padding: String 48 | if let note = note { 49 | let paddingCount = max(1, min(40, noteColumn) - column1.count + 1) 50 | padding = String(repeating: " ", count: paddingCount) 51 | column2 = "# " + note 52 | } else { 53 | padding = "" 54 | column2 = "" 55 | } 56 | return column1 + padding + column2 57 | } 58 | 59 | func formatFirstColumn() -> String { 60 | let indent = String(repeating: " ", count: level * 3) 61 | let hex = data.map { $0.hex }.filter { !$0.isEmpty }.joined(separator: " ") 62 | return indent + hex 63 | } 64 | } 65 | 66 | extension CBOR { 67 | func dumpItems(level: Int, knownTags: TagsStoreProtocol?) -> [DumpItem] { 68 | switch self { 69 | case .unsigned(let n): 70 | return [DumpItem(level: level, data: self.cborData, note: "unsigned(\(n))")] 71 | case .negative(let n): 72 | return [DumpItem(level: level, data: self.cborData, note: "negative(\(-1 - BigInt(n)))")] 73 | case .bytes(let d): 74 | var items = [ 75 | DumpItem(level: level, data: d.count.encodeVarInt(.bytes), note: "bytes(\(d.count))") 76 | ] 77 | if !d.isEmpty { 78 | let note = d.utf8?.sanitized?.flanked("\"") 79 | items.append(DumpItem(level: level + 1, data: d, note: note)) 80 | } 81 | return items 82 | case .text(let s): 83 | let header = s.count.encodeVarInt(.text) 84 | let headerData = [Data(of: header.first!), header.dropFirst()] 85 | return [ 86 | DumpItem(level: level, data: headerData, note: "text(\(s.utf8Data.count))"), 87 | DumpItem(level: level + 1, data: s.utf8Data, note: s.flanked("\"")) 88 | ] 89 | case .simple(let v): 90 | let data = v.cborData 91 | let note = v.description 92 | return [ 93 | DumpItem(level: level, data: data, note: note) 94 | ] 95 | case .tagged(let tag, let item): 96 | let header = tag.value.encodeVarInt(.tagged) 97 | let headerData = [Data(of: header.first!), header.dropFirst()] 98 | var noteComponents: [String] = [ "tag(\(tag.value))" ] 99 | if let name = knownTags?.assignedName(for: tag) { 100 | noteComponents.append(name) 101 | } 102 | let tagNote = noteComponents.joined(separator: " ") 103 | return [ 104 | [ 105 | DumpItem(level: level, data: headerData, note: tagNote) 106 | ], 107 | item.dumpItems(level: level + 1, knownTags: knownTags) 108 | ].flatMap { $0 } 109 | case .array(let array): 110 | let header = array.count.encodeVarInt(.array) 111 | let headerData = [Data(of: header.first!), header.dropFirst()] 112 | return [ 113 | [ 114 | DumpItem(level: level, data: headerData, note: "array(\(array.count))") 115 | ], 116 | array.flatMap { $0.dumpItems(level: level + 1, knownTags: knownTags) } 117 | ].flatMap { $0 } 118 | case .map(let m): 119 | let header = m.count.encodeVarInt(.map) 120 | let headerData = [Data(of: header.first!), header.dropFirst()] 121 | return [ 122 | [ 123 | DumpItem(level: level, data: headerData, note: "map(\(m.count))") 124 | ], 125 | m.entries.flatMap { 126 | [ 127 | $0.key.dumpItems(level: level + 1, knownTags: knownTags), 128 | $0.value.dumpItems(level: level + 1, knownTags: knownTags) 129 | ].flatMap { $0 } 130 | } 131 | ].flatMap { $0 } 132 | } 133 | } 134 | } 135 | 136 | extension Character { 137 | var isPrintable: Bool { 138 | !isASCII || (32...126).contains(asciiValue!) 139 | } 140 | } 141 | 142 | extension String { 143 | var sanitized: String? { 144 | var hasPrintable = false 145 | let s = self.map { c -> Character in 146 | if c.isPrintable { 147 | hasPrintable = true 148 | return c 149 | } else { 150 | return "." 151 | } 152 | } 153 | return !hasPrintable ? nil : String(s) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /Sources/DCBOR/Int.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import NumberKit 3 | 4 | func decodeFixedWidthInteger(_ cbor: CBOR) throws -> T { 5 | let result: T 6 | switch cbor { 7 | case .unsigned(let n): 8 | guard let n = T(exactly: n) else { 9 | throw CBORError.outOfRange 10 | } 11 | result = n 12 | case .negative(let n): 13 | let b = -1 - BigInt(n) 14 | guard let n = T(exactly: b) else { 15 | throw CBORError.outOfRange 16 | } 17 | result = n 18 | default: 19 | throw CBORError.wrongType 20 | } 21 | return result 22 | } 23 | 24 | extension UInt8: CBORCodable { 25 | public var cbor: CBOR { 26 | .unsigned(UInt64(self)) 27 | } 28 | 29 | public var cborData: Data { 30 | self.encodeVarInt(.unsigned) 31 | } 32 | 33 | public init(cbor: CBOR) throws { 34 | self = try decodeFixedWidthInteger(cbor) 35 | } 36 | } 37 | 38 | extension UInt16: CBORCodable { 39 | public var cbor: CBOR { 40 | .unsigned(UInt64(self)) 41 | } 42 | 43 | public var cborData: Data { 44 | self.encodeVarInt(.unsigned) 45 | } 46 | 47 | public init(cbor: CBOR) throws { 48 | self = try decodeFixedWidthInteger(cbor) 49 | } 50 | } 51 | 52 | extension UInt32: CBORCodable { 53 | public var cbor: CBOR { 54 | .unsigned(UInt64(self)) 55 | } 56 | 57 | public var cborData: Data { 58 | self.encodeVarInt(.unsigned) 59 | } 60 | 61 | public init(cbor: CBOR) throws { 62 | self = try decodeFixedWidthInteger(cbor) 63 | } 64 | } 65 | 66 | extension UInt64: CBORCodable { 67 | public var cbor: CBOR { 68 | .unsigned(UInt64(self)) 69 | } 70 | 71 | public var cborData: Data { 72 | self.encodeVarInt(.unsigned) 73 | } 74 | 75 | public init(cbor: CBOR) throws { 76 | self = try decodeFixedWidthInteger(cbor) 77 | } 78 | } 79 | 80 | extension UInt: CBORCodable { 81 | public var cbor: CBOR { 82 | .unsigned(UInt64(self)) 83 | } 84 | 85 | public var cborData: Data { 86 | self.encodeVarInt(.unsigned) 87 | } 88 | 89 | public init(cbor: CBOR) throws { 90 | self = try decodeFixedWidthInteger(cbor) 91 | } 92 | } 93 | 94 | extension Int8: CBORCodable { 95 | public var cbor: CBOR { 96 | if self < 0 { 97 | let b = Int16(self) 98 | let a = UInt64(-1 - b) 99 | return .negative(a) 100 | } else { 101 | return .unsigned(UInt64(self)) 102 | } 103 | } 104 | 105 | public var cborData: Data { 106 | if self < 0 { 107 | let b = Int16(self) 108 | let a = UInt8(-1 - b) 109 | return a.encodeVarInt(.negative) 110 | } else { 111 | let a = UInt8(self) 112 | return a.encodeVarInt(.unsigned) 113 | } 114 | } 115 | 116 | public init(cbor: CBOR) throws { 117 | self = try decodeFixedWidthInteger(cbor) 118 | } 119 | } 120 | 121 | extension Int16: CBORCodable { 122 | public var cbor: CBOR { 123 | if self < 0 { 124 | let b = Int32(self) 125 | let a = UInt64(-1 - b) 126 | return .negative(a) 127 | } else { 128 | return .unsigned(UInt64(self)) 129 | } 130 | } 131 | 132 | public var cborData: Data { 133 | if self < 0 { 134 | let b = Int32(self) 135 | let a = UInt16(-1 - b) 136 | return a.encodeVarInt(.negative) 137 | } else { 138 | let a = UInt16(self) 139 | return a.encodeVarInt(.unsigned) 140 | } 141 | } 142 | 143 | public init(cbor: CBOR) throws { 144 | self = try decodeFixedWidthInteger(cbor) 145 | } 146 | } 147 | 148 | extension Int32: CBORCodable { 149 | public var cbor: CBOR { 150 | if self < 0 { 151 | let b = Int64(self) 152 | let a = UInt64(-1 - b) 153 | return .negative(a) 154 | } else { 155 | return .unsigned(UInt64(self)) 156 | } 157 | } 158 | 159 | public var cborData: Data { 160 | if self < 0 { 161 | let b = Int64(self) 162 | let a = UInt32(-b - 1) 163 | return a.encodeVarInt(.negative) 164 | } else { 165 | let a = UInt32(self) 166 | return a.encodeVarInt(.unsigned) 167 | } 168 | } 169 | 170 | public init(cbor: CBOR) throws { 171 | self = try decodeFixedWidthInteger(cbor) 172 | } 173 | } 174 | 175 | extension Int64: CBORCodable { 176 | public var cbor: CBOR { 177 | if self < 0 { 178 | if self == Int64.min { 179 | return .negative(UInt64(Int64.max)) 180 | } else { 181 | return .negative(UInt64(-1 - self)) 182 | } 183 | } else { 184 | return .unsigned(UInt64(self)) 185 | } 186 | } 187 | 188 | public var cborData: Data { 189 | if self < 0 { 190 | if self == Int64.min { 191 | return UInt64(Int64.max).encodeVarInt(.negative) 192 | } else { 193 | let a = UInt64(-self - 1) 194 | return a.encodeVarInt(.negative) 195 | } 196 | } else { 197 | let a = UInt32(self) 198 | return a.encodeVarInt(.unsigned) 199 | } 200 | } 201 | 202 | public init(cbor: CBOR) throws { 203 | self = try decodeFixedWidthInteger(cbor) 204 | } 205 | } 206 | 207 | extension Int: CBORCodable { 208 | public var cbor: CBOR { 209 | if self < 0 { 210 | if self == Int.min { 211 | return .negative(UInt64(Int.max)) 212 | } else { 213 | return .negative(UInt64(-1 - self)) 214 | } 215 | } else { 216 | return .unsigned(UInt64(self)) 217 | } 218 | } 219 | 220 | public var cborData: Data { 221 | Int64(self).cborData 222 | } 223 | 224 | public init(cbor: CBOR) throws { 225 | self = try decodeFixedWidthInteger(cbor) 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /Sources/DCBOR/Float.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import NumberKit 3 | 4 | let cborNaN = Data([0xf9, 0x7e, 0x00]) 5 | 6 | extension Double: CBORCodable { 7 | public var cbor: CBOR { 8 | /* 9 | if 10 | self < 0, 11 | let n = BigInt(exactly: self), 12 | let i = UInt64(exactly: -1 - n) 13 | { 14 | return .negative(i) 15 | } 16 | if let i = UInt64(exactly: self) { 17 | return i.cbor 18 | } 19 | */ 20 | return .simple(.float(self)) 21 | } 22 | 23 | public init(cbor: CBOR) throws { 24 | switch cbor { 25 | case .unsigned(let n): 26 | guard let f = Double(exactly: n) else { 27 | throw CBORError.outOfRange 28 | } 29 | self = f 30 | case .negative(let n): 31 | let b = -1 - BigInt(n) 32 | guard let f = Double(exactly: b) else { 33 | throw CBORError.outOfRange 34 | } 35 | self = f 36 | case .simple(let simple): 37 | guard case .float(let f) = simple else { 38 | throw CBORError.wrongType 39 | } 40 | self = f 41 | default: 42 | throw CBORError.wrongType 43 | } 44 | } 45 | 46 | public var cborData: Data { 47 | let f = Float(self) 48 | guard self != Double(f) else { 49 | return f.cborData 50 | } 51 | /* 52 | if 53 | self < 0, 54 | let i = Int64(exactly: self) 55 | { 56 | return i.cborData 57 | } 58 | if let i = UInt64(exactly: self) { 59 | return i.cborData 60 | } 61 | */ 62 | guard !isNaN else { 63 | return cborNaN 64 | } 65 | return self.bitPattern.encodeInt(.simple) 66 | } 67 | 68 | func validateCanonical() throws { 69 | guard 70 | self != Double(Float(self)), 71 | // Int64(exactly: self) == nil, 72 | !isNaN 73 | else { 74 | throw CBORError.nonCanonicalNumeric 75 | } 76 | } 77 | } 78 | 79 | extension Float: CBORCodable { 80 | public var cbor: CBOR { 81 | /* 82 | if 83 | self < 0, 84 | let n = BigInt(exactly: self), 85 | let i = UInt64(exactly: -1 - n) 86 | { 87 | return .negative(i) 88 | } 89 | if let i = UInt64(exactly: self) { 90 | return i.cbor 91 | } 92 | */ 93 | return .simple(.float(Double(self))) 94 | } 95 | 96 | public init(cbor: CBOR) throws { 97 | switch cbor { 98 | /* 99 | case .unsigned(let n): 100 | guard let f = Float(exactly: n) else { 101 | throw CBORError.outOfRange 102 | } 103 | self = f 104 | case .negative(let n): 105 | let b = -1 - BigInt(n) 106 | guard let f = Float(exactly: b) else { 107 | throw CBORError.outOfRange 108 | } 109 | self = f 110 | */ 111 | case .simple(let simple): 112 | guard case .float(let f) = simple else { 113 | throw CBORError.wrongType 114 | } 115 | guard let f = Float(exactly: f) else { 116 | throw CBORError.outOfRange 117 | } 118 | self = f 119 | default: 120 | throw CBORError.wrongType 121 | } 122 | } 123 | 124 | public var cborData: Data { 125 | let f = CBORFloat16(self) 126 | guard self != Float(f) else { 127 | return f.cborData 128 | } 129 | /* 130 | if 131 | self < 0, 132 | let i = Int64(exactly: self) 133 | { 134 | return i.cborData 135 | } 136 | if let i = UInt64(exactly: self) { 137 | return i.cborData 138 | } 139 | */ 140 | guard !isNaN else { 141 | return cborNaN 142 | } 143 | return self.bitPattern.encodeInt(.simple) 144 | } 145 | 146 | func validateCanonical() throws { 147 | guard 148 | self != Float(CBORFloat16(self)), 149 | !isNaN 150 | else { 151 | throw CBORError.nonCanonicalNumeric 152 | } 153 | } 154 | } 155 | 156 | extension CBORFloat16: CBORCodable { 157 | public var cbor: CBOR { 158 | /* 159 | if 160 | Float(self) < 0, 161 | let i = Int64(exactly: self) 162 | { 163 | return i.cbor 164 | } 165 | if let i = UInt64(exactly: self) { 166 | return i.cbor 167 | } 168 | */ 169 | return .simple(.float(Double(self))) 170 | } 171 | 172 | public init(cbor: CBOR) throws { 173 | switch cbor { 174 | case .unsigned(let n): 175 | guard let f = CBORFloat16(exactly: n) else { 176 | throw CBORError.outOfRange 177 | } 178 | self = f 179 | case .negative(let n): 180 | let b = -1 - BigInt(n) 181 | guard let f = CBORFloat16(exactly: b) else { 182 | throw CBORError.outOfRange 183 | } 184 | self = f 185 | case .simple(let simple): 186 | guard case .float(let f) = simple else { 187 | throw CBORError.wrongType 188 | } 189 | guard let f = CBORFloat16(exactly: f) else { 190 | throw CBORError.outOfRange 191 | } 192 | self = f 193 | default: 194 | throw CBORError.wrongType 195 | } 196 | } 197 | 198 | public var cborData: Data { 199 | /* 200 | if 201 | Float(self) < 0, 202 | let i = Int64(exactly: self) 203 | { 204 | return i.cborData 205 | } 206 | if let i = UInt64(exactly: self) { 207 | return i.cborData 208 | } 209 | */ 210 | guard !isNaN else { 211 | return cborNaN 212 | } 213 | return self.bitPattern.encodeInt(.simple) 214 | } 215 | 216 | func validateCanonical() throws { 217 | guard 218 | !isNaN || self.bitPattern == 0x7e00 219 | else { 220 | throw CBORError.nonCanonicalNumeric 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /Sources/DCBOR/Decode.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | func decodeCBOR(_ data: Data) throws -> CBOR { 4 | let (cbor, len) = try decodeCBORInternal(ArraySlice(data)) 5 | let remaining = data.count - len 6 | guard remaining == 0 else { 7 | throw CBORError.unusedData(remaining) 8 | } 9 | return cbor 10 | } 11 | 12 | extension ArraySlice where Element == UInt8 { 13 | func at(_ index: Int) -> UInt8 { 14 | self[startIndex + index] 15 | } 16 | 17 | func from(_ index: Int) -> ArraySlice { 18 | self[(startIndex + index)...] 19 | } 20 | 21 | func range(_ range: Range) -> ArraySlice { 22 | self[(startIndex + range.lowerBound)..<(startIndex + range.upperBound)] 23 | } 24 | } 25 | 26 | func parseHeader(_ header: UInt8) -> (MajorType, UInt8) { 27 | let majorType: MajorType 28 | switch header >> 5 { 29 | case 0: 30 | majorType = .unsigned 31 | case 1: 32 | majorType = .negative 33 | case 2: 34 | majorType = .bytes 35 | case 3: 36 | majorType = .text 37 | case 4: 38 | majorType = .array 39 | case 5: 40 | majorType = .map 41 | case 6: 42 | majorType = .tagged 43 | case 7: 44 | majorType = .simple 45 | default: 46 | preconditionFailure() 47 | } 48 | let headerValue = header & 31 49 | return (majorType, headerValue) 50 | } 51 | 52 | func parseHeaderVarint(_ data: ArraySlice) throws -> (majorType: MajorType, value: UInt64, varIntLen: Int) { 53 | guard !data.isEmpty else { 54 | throw CBORError.underrun 55 | } 56 | 57 | let header = data.at(0) 58 | let (majorType, headerValue) = parseHeader(header) 59 | let dataRemaining = data.count - 1 60 | let value: UInt64 61 | let varIntLen: Int 62 | switch headerValue { 63 | case 0...23: 64 | value = UInt64(headerValue) 65 | varIntLen = 1 66 | case 24: 67 | guard dataRemaining >= 1 else { 68 | throw CBORError.underrun 69 | } 70 | value = UInt64(data.at(1)) 71 | guard value >= 24 else { 72 | throw CBORError.nonCanonicalNumeric 73 | } 74 | varIntLen = 2 75 | case 25: 76 | guard dataRemaining >= 2 else { 77 | throw CBORError.underrun 78 | } 79 | value = 80 | UInt64(data.at(1)) << 8 | 81 | UInt64(data.at(2)) 82 | guard value > UInt8.max || header == 0xf9 else { 83 | throw CBORError.nonCanonicalNumeric 84 | } 85 | varIntLen = 3 86 | case 26: 87 | guard dataRemaining >= 4 else { 88 | throw CBORError.underrun 89 | } 90 | value = 91 | UInt64(data.at(1)) << 24 | 92 | UInt64(data.at(2)) << 16 | 93 | UInt64(data.at(3)) << 8 | 94 | UInt64(data.at(4)) 95 | guard value > UInt16.max || header == 0xfa else { 96 | throw CBORError.nonCanonicalNumeric 97 | } 98 | varIntLen = 5 99 | case 27: 100 | guard dataRemaining >= 8 else { 101 | throw CBORError.underrun 102 | } 103 | let valHi = 104 | UInt64(data.at(1)) << 56 | 105 | UInt64(data.at(2)) << 48 | 106 | UInt64(data.at(3)) << 40 | 107 | UInt64(data.at(4)) << 32 108 | 109 | let valLo = 110 | UInt64(data.at(5)) << 24 | 111 | UInt64(data.at(6)) << 16 | 112 | UInt64(data.at(7)) << 8 | 113 | UInt64(data.at(8)) 114 | 115 | value = valHi | valLo 116 | 117 | guard value > UInt32.max || header == 0xfb else { 118 | throw CBORError.nonCanonicalNumeric 119 | } 120 | varIntLen = 9 121 | default: 122 | throw CBORError.badHeaderValue(encountered: headerValue) 123 | } 124 | return (majorType, value, varIntLen) 125 | } 126 | 127 | func parseBytes(_ data: ArraySlice, len: Int) throws -> ArraySlice { 128 | guard !data.isEmpty else { 129 | throw CBORError.underrun 130 | } 131 | return data.range(0..) throws -> (cbor: CBOR, len: Int) { 135 | guard !data.isEmpty else { 136 | throw CBORError.underrun 137 | } 138 | let (majorType, value, headerVarIntLen) = try parseHeaderVarint(data) 139 | switch majorType { 140 | case .unsigned: 141 | return (.unsigned(value), headerVarIntLen) 142 | case .negative: 143 | return (.negative(value), headerVarIntLen) 144 | case .bytes: 145 | let dataLen = Int(value) 146 | let buf = try parseBytes(data.from(headerVarIntLen), len: dataLen) 147 | let bytes = Data(buf) 148 | return (bytes.cbor, headerVarIntLen + dataLen) 149 | case .text: 150 | let dataLen = Int(value) 151 | let buf = try parseBytes(data.from(headerVarIntLen), len: dataLen) 152 | guard let string = String(bytes: buf, encoding: .utf8) else { 153 | throw CBORError.invalidString 154 | } 155 | return (string.cbor, headerVarIntLen + dataLen) 156 | case .array: 157 | var pos = headerVarIntLen 158 | var items: [CBOR] = [] 159 | for _ in 0.. String { 10 | diagItem(annotate: annotate, knownTags: tags).format(annotate: annotate) 11 | } 12 | } 13 | 14 | extension Array where Element == String { 15 | func joined(itemSeparator: String = "", pairSeparator: String? = nil) -> String { 16 | let pairSeparator = pairSeparator ?? itemSeparator 17 | var result = "" 18 | for (index, item) in self.enumerated() { 19 | result += item 20 | if index != count - 1 { 21 | if index & 1 != 0 { 22 | result += itemSeparator 23 | } else { 24 | result += pairSeparator 25 | } 26 | } 27 | } 28 | return result 29 | } 30 | } 31 | 32 | enum DiagItem { 33 | case item(String) 34 | case group(begin: String, end: String, items: [DiagItem], isPairs: Bool, comment: String?) 35 | 36 | func format(level: Int = 0, separator: String = "", annotate: Bool = false) -> String { 37 | switch self { 38 | case .item(let string): 39 | return formatLine(level: level, string: string, separator: separator) 40 | case .group: 41 | if containsGroup || totalStringsLength > 20 || greatestStringsLength > 20 { 42 | return multilineComposition(level: level, separator: separator, annotate: annotate) 43 | } else { 44 | return singleLineComposition(level: level, separator: separator, annotate: annotate) 45 | } 46 | } 47 | } 48 | 49 | private func formatLine(level: Int, string: String, separator: String = "", comment: String? = nil) -> String { 50 | var result = String(repeating: " ", count: level * 3) + string + separator 51 | if let comment { 52 | result += " / \(comment) /" 53 | } 54 | return result 55 | } 56 | 57 | func singleLineComposition(level: Int, separator: String, annotate: Bool) -> String { 58 | let string: String 59 | let comment: String? 60 | switch self { 61 | case .item(let s): 62 | string = s 63 | comment = nil 64 | case .group(let begin, let end, let items, let isPairs, let comm): 65 | let components = items.map { item -> String in 66 | switch item { 67 | case .item(let string): 68 | return string 69 | case .group: 70 | return "" 71 | } 72 | } 73 | let pairSeparator = isPairs ? ": " : ", " 74 | string = components.joined(itemSeparator: ", ", pairSeparator: pairSeparator).flanked(begin, end) 75 | comment = comm 76 | } 77 | return formatLine(level: level, string: string, separator: separator, comment: comment) 78 | } 79 | 80 | func multilineComposition(level: Int, separator: String, annotate: Bool) -> String { 81 | switch self { 82 | case .item(let string): 83 | return string 84 | case .group(let begin, let end, let items, let isPairs, let comment): 85 | var lines: [String] = [] 86 | lines.append(formatLine(level: level, string: begin, comment: comment)) 87 | for (index, item) in items.enumerated() { 88 | let separator = index == items.count - 1 ? "" : (isPairs && index & 1 == 0 ? ":" : ",") 89 | lines.append(item.format(level: level + 1, separator: separator, annotate: annotate)) 90 | } 91 | lines.append(formatLine(level: level, string: end, separator: separator)) 92 | return lines.joined(separator: "\n") 93 | } 94 | } 95 | 96 | var totalStringsLength: Int { 97 | switch self { 98 | case .item(let string): 99 | return string.count 100 | case .group(_, _, let items, _, _): 101 | return items.reduce(into: 0) { result, item in 102 | result += item.totalStringsLength 103 | } 104 | } 105 | } 106 | 107 | var greatestStringsLength: Int { 108 | switch self { 109 | case .item(let string): 110 | return string.count 111 | case .group(_, _, let items, _, _): 112 | return items.reduce(into: 0) { result, item in 113 | result = max(result, item.totalStringsLength) 114 | } 115 | } 116 | } 117 | 118 | var isGroup: Bool { 119 | if case .group = self { 120 | return true 121 | } else { 122 | return false 123 | } 124 | } 125 | 126 | var containsGroup: Bool { 127 | switch self { 128 | case .item: 129 | return false 130 | case .group(_, _, let items, _, _): 131 | return items.first { $0.isGroup } != nil 132 | } 133 | } 134 | } 135 | 136 | extension CBOR { 137 | func diagItem(annotate: Bool = false, knownTags: TagsStoreProtocol?) -> DiagItem { 138 | switch self { 139 | case .unsigned, .negative, .bytes, .text, .simple: 140 | return .item(description) 141 | case .tagged(let tag, let item): 142 | let diagItem: DiagItem 143 | if annotate && tag == 1 { 144 | if let n = try? Double(cbor: item) { 145 | diagItem = .item(ISO8601DateFormatter().string(from: Date(timeIntervalSince1970: TimeInterval(n)))) 146 | } else { 147 | diagItem = item.diagItem(annotate: annotate, knownTags: knownTags) 148 | } 149 | } else { 150 | diagItem = item.diagItem(annotate: annotate, knownTags: knownTags) 151 | } 152 | return .group( 153 | begin: String(tag.value) + "(", 154 | end: ")", 155 | items: [diagItem], 156 | isPairs: false, 157 | comment: knownTags?.assignedName(for: tag) 158 | ) 159 | case .array(let a): 160 | return .group( 161 | begin: "[", 162 | end: "]", 163 | items: a.map { $0.diagItem(annotate: annotate, knownTags: knownTags) }, 164 | isPairs: false, 165 | comment: nil 166 | ) 167 | case .map(let m): 168 | return .group( 169 | begin: "{", 170 | end: "}", 171 | items: m.map { (key, value) in [ 172 | key.diagItem(annotate: annotate, knownTags: knownTags), 173 | value.diagItem(annotate: annotate, knownTags: knownTags) 174 | ]}.flatMap { $0 }, 175 | isPairs: true, 176 | comment: nil 177 | ) 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blockchain Commons Deterministic CBOR ("DCBOR") Codec for Swift 2 | 3 | 4 | 5 | ### _by Wolf McNally_ 6 | 7 | BCSwiftDCBOR is a pure Swift [CBOR](https://cbor.io) codec that focuses on writing and parsing "deterministic" CBOR per [§4.2 of RFC-8949](https://www.rfc-editor.org/rfc/rfc8949.html#name-deterministically-encoded-c). It does not support parts of the spec forbidden by deterministic CBOR (such as indefinite length arrays and maps). It is strict in both what it writes and reads: in particular it will throw decoding errors if variable-length integers are not encoded in their minimal form, or CBOR map keys are not in lexicographic order, or there is extra data past the end of the decoded CBOR item. 8 | 9 | ## _Specification_ 10 | 11 | This repository contains a patched version of DCBOR that is aligned with https://datatracker.ietf.org/doc/draft-ietf-cbor-cde. 12 | 13 | ## Related Projects 14 | 15 | * [dCBOR Overview](https://github.com/BlockchainCommons/crypto-commons/blob/master/dcbor.md) 16 | * [dCBOR Library for Rust](https://github.com/BlockchainCommons/bc-dcbor-rust) 17 | * [dCBOR-CLI Reference App](https://github.com/BlockchainCommons/dcbor-cli) 18 | 19 | ## Status - Alpha 20 | 21 | `BCSwiftDCBOR` is currently under active development and in the alpha testing phase. It should not be used for production tasks until it has had further testing and auditing. See [Blockchain Commons' Development Phases](https://github.com/BlockchainCommons/Community/blob/master/release-path.md). 22 | 23 | ## Installation Instructions 24 | 25 | Add to your project like any Swift package. 26 | 27 | ## Financial Support 28 | 29 | BCSwiftDCBOR is a project of [Blockchain Commons](https://www.blockchaincommons.com/). We are proudly a "not-for-profit" social benefit corporation committed to open source & open development. Our work is funded entirely by donations and collaborative partnerships with people like you. Every contribution will be spent on building open tools, technologies, and techniques that sustain and advance blockchain and internet security infrastructure and promote an open web. 30 | 31 | To financially support further development of BCSwiftDCBOR and other projects, please consider becoming a Patron of Blockchain Commons through ongoing monthly patronage as a [GitHub Sponsor](https://github.com/sponsors/BlockchainCommons). You can also support Blockchain Commons with bitcoins at our [BTCPay Server](https://btcpay.blockchaincommons.com/). 32 | 33 | ## Contributing 34 | 35 | We encourage public contributions through issues and pull requests! Please review [CONTRIBUTING.md](./CONTRIBUTING.md) for details on our development process. All contributions to this repository require a GPG signed [Contributor License Agreement](./CLA.md). 36 | 37 | ### Discussions 38 | 39 | The best place to talk about Blockchain Commons and its projects is in our GitHub Discussions areas. 40 | 41 | [**Gordian Developer Community**](https://github.com/BlockchainCommons/Gordian-Developer-Community/discussions). For standards and open-source developers who want to talk about interoperable wallet specifications, please use the Discussions area of the [Gordian Developer Community repo](https://github.com/BlockchainCommons/Gordian-Developer-Community/discussions). This is where you talk about Gordian specifications such as [Gordian Envelope](https://github.com/BlockchainCommons/Gordian/tree/master/Envelope#articles), [bc-shamir](https://github.com/BlockchainCommons/bc-shamir), [Sharded Secret Key Reconstruction](https://github.com/BlockchainCommons/bc-sskr), and [bc-ur](https://github.com/BlockchainCommons/bc-ur) as well as the larger [Gordian Architecture](https://github.com/BlockchainCommons/Gordian/blob/master/Docs/Overview-Architecture.md), its [Principles](https://github.com/BlockchainCommons/Gordian#gordian-principles) of independence, privacy, resilience, and openness, and its macro-architectural ideas such as functional partition (including airgapping, the original name of this community). 42 | 43 | [**Gordian User Community**](https://github.com/BlockchainCommons/Gordian/discussions). For users of the Gordian reference apps, including [Gordian Coordinator](https://github.com/BlockchainCommons/iOS-GordianCoordinator), [Gordian Seed Tool](https://github.com/BlockchainCommons/GordianSeedTool-iOS), [Gordian Server](https://github.com/BlockchainCommons/GordianServer-macOS), [Gordian Wallet](https://github.com/BlockchainCommons/GordianWallet-iOS), and [SpotBit](https://github.com/BlockchainCommons/spotbit) as well as our whole series of [CLI apps](https://github.com/BlockchainCommons/Gordian/blob/master/Docs/Overview-Apps.md#cli-apps). This is a place to talk about bug reports and feature requests as well as to explore how our reference apps embody the [Gordian Principles](https://github.com/BlockchainCommons/Gordian#gordian-principles). 44 | 45 | [**Blockchain Commons Discussions**](https://github.com/BlockchainCommons/Community/discussions). For developers, interns, and patrons of Blockchain Commons, please use the discussions area of the [Community repo](https://github.com/BlockchainCommons/Community) to talk about general Blockchain Commons issues, the intern program, or topics other than those covered by the [Gordian Developer Community](https://github.com/BlockchainCommons/Gordian-Developer-Community/discussions) or the 46 | [Gordian User Community](https://github.com/BlockchainCommons/Gordian/discussions). 47 | 48 | ### Other Questions & Problems 49 | 50 | As an open-source, open-development community, Blockchain Commons does not have the resources to provide direct support of our projects. Please consider the discussions area as a locale where you might get answers to questions. Alternatively, please use this repository's [issues](./issues) feature. Unfortunately, we can not make any promises on response time. 51 | 52 | If your company requires support to use our projects, please feel free to contact us directly about options. We may be able to offer you a contract for support from one of our contributors, or we might be able to point you to another entity who can offer the contractual support that you need. 53 | 54 | ### Credits 55 | 56 | The following people directly contributed to this repository. You can add your name here by getting involved. The first step is learning how to contribute from our [CONTRIBUTING.md](./CONTRIBUTING.md) documentation. 57 | 58 | | Name | Role | Github | Email | GPG Fingerprint | 59 | | ----------------- | ------------------- | ------------------------------------------------- | ------------------------------------- | -------------------------------------------------- | 60 | | Christopher Allen | Principal Architect | [@ChristopherA](https://github.com/ChristopherA) | \ | FDFE 14A5 4ECB 30FC 5D22 74EF F8D3 6C91 3574 05ED | 61 | | Wolf McNally | Lead Researcher/Engineer | [@WolfMcNally](https://github.com/WolfMcNally) | \ | 9436 52EE 3844 1760 C3DC  3536 4B6C 2FCF 8947 80AE | 62 | 63 | ## Responsible Disclosure 64 | 65 | We want to keep all of our software safe for everyone. If you have discovered a security vulnerability, we appreciate your help in disclosing it to us in a responsible manner. We are unfortunately not able to offer bug bounties at this time. 66 | 67 | We do ask that you offer us good faith and use best efforts not to leak information or harm any user, their data, or our developer community. Please give us a reasonable amount of time to fix the issue before you publish it. Do not defraud our users or us in the process of discovery. We promise not to bring legal action against researchers who point out a problem provided they do their best to follow the these guidelines. 68 | 69 | ### Reporting a Vulnerability 70 | 71 | Please report suspected security vulnerabilities in private via email to ChristopherA@BlockchainCommons.com (do not use this email for support). Please do NOT create publicly viewable issues for suspected security vulnerabilities. 72 | 73 | The following keys may be used to communicate sensitive information to developers: 74 | 75 | | Name | Fingerprint | 76 | | ----------------- | -------------------------------------------------- | 77 | | Christopher Allen | FDFE 14A5 4ECB 30FC 5D22 74EF F8D3 6C91 3574 05ED | 78 | 79 | You can import a key by running the following command with that individual’s fingerprint: `gpg --recv-keys ""` Ensure that you put quotes around fingerprints that contain spaces. 80 | -------------------------------------------------------------------------------- /Tests/DCBORTests/CodingTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import WolfBase 3 | import DCBOR 4 | 5 | final class CodingTests: XCTestCase { 6 | func runTest(_ t: T, _ expectedDebugDescription: String, _ expectedDescription: String, _ expectedData: String) where T: CBORCodable & Equatable { 7 | let cbor = t.cbor 8 | XCTAssertEqual(cbor.debugDescription, expectedDebugDescription) 9 | XCTAssertEqual(cbor.description, expectedDescription) 10 | let data = cbor.cborData 11 | XCTAssertEqual(data.hex, expectedData.lowercased()) 12 | let decodedCBOR = try! CBOR(data) 13 | XCTAssertEqual(cbor, decodedCBOR) 14 | let decodedT = try! T(cbor: cbor) 15 | XCTAssertEqual(t, decodedT) 16 | } 17 | 18 | func testUnsigned() throws { 19 | runTest(UInt8 (0), "unsigned(0)", "0", "00") 20 | runTest(UInt16(0), "unsigned(0)", "0", "00") 21 | runTest(UInt32(0), "unsigned(0)", "0", "00") 22 | runTest(UInt64(0), "unsigned(0)", "0", "00") 23 | runTest(UInt (0), "unsigned(0)", "0", "00") 24 | 25 | runTest(UInt8 (1), "unsigned(1)", "1", "01") 26 | runTest(UInt16(1), "unsigned(1)", "1", "01") 27 | runTest(UInt32(1), "unsigned(1)", "1", "01") 28 | runTest(UInt64(1), "unsigned(1)", "1", "01") 29 | runTest(UInt (1), "unsigned(1)", "1", "01") 30 | 31 | runTest(UInt8 (23), "unsigned(23)", "23", "17") 32 | runTest(UInt16(23), "unsigned(23)", "23", "17") 33 | runTest(UInt32(23), "unsigned(23)", "23", "17") 34 | runTest(UInt64(23), "unsigned(23)", "23", "17") 35 | runTest(UInt (23), "unsigned(23)", "23", "17") 36 | 37 | runTest(UInt8 (24), "unsigned(24)", "24", "1818") 38 | runTest(UInt16(24), "unsigned(24)", "24", "1818") 39 | runTest(UInt32(24), "unsigned(24)", "24", "1818") 40 | runTest(UInt64(24), "unsigned(24)", "24", "1818") 41 | runTest(UInt (24), "unsigned(24)", "24", "1818") 42 | 43 | runTest(UInt8 .max, "unsigned(255)", "255", "18ff") 44 | runTest(UInt16(UInt8.max), "unsigned(255)", "255", "18ff") 45 | runTest(UInt32(UInt8.max), "unsigned(255)", "255", "18ff") 46 | runTest(UInt64(UInt8.max), "unsigned(255)", "255", "18ff") 47 | runTest(UInt (UInt8.max), "unsigned(255)", "255", "18ff") 48 | 49 | runTest(UInt16 .max, "unsigned(65535)", "65535", "19ffff") 50 | runTest(UInt32(UInt16.max), "unsigned(65535)", "65535", "19ffff") 51 | runTest(UInt64(UInt16.max), "unsigned(65535)", "65535", "19ffff") 52 | runTest(UInt (UInt16.max), "unsigned(65535)", "65535", "19ffff") 53 | 54 | runTest(UInt32(65536), "unsigned(65536)", "65536", "1a00010000") 55 | runTest(UInt64(65536), "unsigned(65536)", "65536", "1a00010000") 56 | runTest(UInt (65536), "unsigned(65536)", "65536", "1a00010000") 57 | 58 | runTest(UInt32 .max, "unsigned(4294967295)", "4294967295", "1affffffff") 59 | runTest(UInt64(UInt32.max), "unsigned(4294967295)", "4294967295", "1affffffff") 60 | runTest(UInt (UInt32.max), "unsigned(4294967295)", "4294967295", "1affffffff") 61 | 62 | runTest(4294967296, "unsigned(4294967296)", "4294967296", "1b0000000100000000") 63 | 64 | runTest(UInt64.max, "unsigned(18446744073709551615)", "18446744073709551615", "1bffffffffffffffff") 65 | runTest(UInt .max, "unsigned(18446744073709551615)", "18446744073709551615", "1bffffffffffffffff") 66 | } 67 | 68 | func testSigned() { 69 | runTest(Int8 (-1), "negative(-1)", "-1", "20") 70 | runTest(Int16(-1), "negative(-1)", "-1", "20") 71 | runTest(Int32(-1), "negative(-1)", "-1", "20") 72 | runTest(Int64(-1), "negative(-1)", "-1", "20") 73 | 74 | runTest(Int8 (-2), "negative(-2)", "-2", "21") 75 | runTest(Int16(-2), "negative(-2)", "-2", "21") 76 | runTest(Int32(-2), "negative(-2)", "-2", "21") 77 | runTest(Int64(-2), "negative(-2)", "-2", "21") 78 | 79 | runTest(Int8 (-127), "negative(-127)", "-127", "387e") 80 | runTest(Int16(-127), "negative(-127)", "-127", "387e") 81 | runTest(Int32(-127), "negative(-127)", "-127", "387e") 82 | runTest(Int64(-127), "negative(-127)", "-127", "387e") 83 | 84 | runTest(Int8 (Int8.min), "negative(-128)", "-128", "387f") 85 | runTest(Int16(Int8.min), "negative(-128)", "-128", "387f") 86 | runTest(Int32(Int8.min), "negative(-128)", "-128", "387f") 87 | runTest(Int64(Int8.min), "negative(-128)", "-128", "387f") 88 | 89 | runTest(Int8 (Int8.max), "unsigned(127)", "127", "187f") 90 | runTest(Int16(Int8.max), "unsigned(127)", "127", "187f") 91 | runTest(Int32(Int8.max), "unsigned(127)", "127", "187f") 92 | runTest(Int64(Int8.max), "unsigned(127)", "127", "187f") 93 | 94 | runTest(Int16(Int16.min), "negative(-32768)", "-32768", "397fff") 95 | runTest(Int32(Int16.min), "negative(-32768)", "-32768", "397fff") 96 | runTest(Int64(Int16.min), "negative(-32768)", "-32768", "397fff") 97 | 98 | runTest(Int16(Int16.max), "unsigned(32767)", "32767", "197fff") 99 | runTest(Int32(Int16.max), "unsigned(32767)", "32767", "197fff") 100 | runTest(Int64(Int16.max), "unsigned(32767)", "32767", "197fff") 101 | 102 | runTest(Int32(Int32.min), "negative(-2147483648)", "-2147483648", "3a7fffffff") 103 | runTest(Int64(Int32.min), "negative(-2147483648)", "-2147483648", "3a7fffffff") 104 | 105 | runTest(Int32(Int32.max), "unsigned(2147483647)", "2147483647", "1a7fffffff") 106 | runTest(Int64(Int32.max), "unsigned(2147483647)", "2147483647", "1a7fffffff") 107 | 108 | runTest(Int64.min, "negative(-9223372036854775808)", "-9223372036854775808", "3b7fffffffffffffff") 109 | 110 | runTest(Int64.max, "unsigned(9223372036854775807)", "9223372036854775807", "1b7fffffffffffffff") 111 | } 112 | 113 | func testBytes() { 114 | runTest(‡"112233", "bytes(112233)", "h'112233'", "43112233") 115 | runTest( 116 | ‡"c0a7da14e5847c526244f7e083d26fe33f86d2313ad2b77164233444423a50a7", 117 | "bytes(c0a7da14e5847c526244f7e083d26fe33f86d2313ad2b77164233444423a50a7)", 118 | "h'c0a7da14e5847c526244f7e083d26fe33f86d2313ad2b77164233444423a50a7'", 119 | "5820c0a7da14e5847c526244f7e083d26fe33f86d2313ad2b77164233444423a50a7") 120 | } 121 | 122 | func testArray() { 123 | runTest([1, 2, 3], "array([unsigned(1), unsigned(2), unsigned(3)])", "[1, 2, 3]", "83010203") 124 | runTest([1, -2, 3], "array([unsigned(1), negative(-2), unsigned(3)])", "[1, -2, 3]", "83012103") 125 | } 126 | 127 | func testMap() throws { 128 | var map = Map() 129 | map.insert(-1, 3) 130 | map.insert([-1], 7) 131 | map.insert("z", 4) 132 | map.insert(10, 1) 133 | map.insert(false, 8) 134 | map.insert(100, 2) 135 | map.insert("aa", 5) 136 | map.insert([100], 6) 137 | runTest(map, 138 | #"map({0x0a: (unsigned(10), unsigned(1)), 0x1864: (unsigned(100), unsigned(2)), 0x20: (negative(-1), unsigned(3)), 0x617a: (text("z"), unsigned(4)), 0x626161: (text("aa"), unsigned(5)), 0x811864: (array([unsigned(100)]), unsigned(6)), 0x8120: (array([negative(-1)]), unsigned(7)), 0xf4: (simple(false), unsigned(8))})"#, 139 | #"{10: 1, 100: 2, -1: 3, "z": 4, "aa": 5, [100]: 6, [-1]: 7, false: 8}"#, 140 | "a80a011864022003617a046261610581186406812007f408") 141 | XCTAssertNil(map[true] as Int?) 142 | XCTAssertEqual(map[-1], 3) 143 | XCTAssertEqual(map[[-1]], 7) 144 | XCTAssertEqual(map["z"], 4) 145 | XCTAssertNil(map["foo"] as Int?) 146 | } 147 | 148 | func testAndersMap() throws { 149 | let map: Map = [ 150 | 1: 45.7, 151 | 2: "Hi there!" 152 | ] 153 | XCTAssertEqual(map.cborData, ‡"a201fb4046d9999999999a0269486920746865726521") 154 | XCTAssertEqual(map[1], 45.7) 155 | } 156 | 157 | func testMisorderedMap() { 158 | let mapWithOutOfOrderKeys = ‡"a8f4080a011864022003617a046261610581186406812007" 159 | XCTAssertThrowsError(try CBOR(mapWithOutOfOrderKeys)) { 160 | guard case CBORError.misorderedMapKey = $0 else { 161 | XCTFail() 162 | return 163 | } 164 | } 165 | } 166 | 167 | func testDuplicateKey() { 168 | let mapWithDuplicateKey = ‡"a90a011864022003617a046261610581186406812007f408f408" 169 | XCTAssertThrowsError(try CBOR(mapWithDuplicateKey)) { 170 | guard case CBORError.duplicateMapKey = $0 else { 171 | XCTFail() 172 | return 173 | } 174 | } 175 | } 176 | 177 | func testString() { 178 | runTest("Hello", #"text("Hello")"#, #""Hello""#, "6548656c6c6f") 179 | } 180 | 181 | func testTagged() { 182 | runTest(Tagged(1, "Hello"), #"tagged(1, text("Hello"))"#, #"1("Hello")"#, "c16548656c6c6f") 183 | } 184 | 185 | func testValue() { 186 | runTest(false, "simple(false)", "false", "f4") 187 | runTest(true, "simple(true)", "true", "f5") 188 | // runTest(Simple(100), "simple(100)", "simple(100)", "f864") 189 | 190 | XCTAssertEqual(CBOR.null.description, "null") 191 | XCTAssertEqual(CBOR.null.debugDescription, "simple(null)") 192 | XCTAssertEqual(CBOR.null.cborData, ‡"f6") 193 | XCTAssertEqual(try! CBOR(‡"f6"), CBOR.null) 194 | } 195 | 196 | func testUnusedData() { 197 | XCTAssertThrowsError(try CBOR(‡"0001")) { 198 | guard 199 | case CBORError.unusedData(let remaining) = $0, 200 | remaining == 1 201 | else { 202 | XCTFail() 203 | return 204 | } 205 | } 206 | } 207 | 208 | func testEnvelope() { 209 | let alice = CBOR.tagged(200, CBOR.tagged(24, "Alice")) 210 | let knows = CBOR.tagged(200, CBOR.tagged(24, "knows")) 211 | let bob = CBOR.tagged(200, CBOR.tagged(24, "Bob")) 212 | let knowsBob = CBOR.tagged(200, CBOR.tagged(221, [knows, bob])) 213 | let envelope = CBOR.tagged(200, [alice, knowsBob]) 214 | XCTAssertEqual(envelope.description, #"200([200(24("Alice")), 200(221([200(24("knows")), 200(24("Bob"))]))])"#) 215 | let bytes = envelope.cborData 216 | XCTAssertEqual(bytes, ‡"d8c882d8c8d81865416c696365d8c8d8dd82d8c8d818656b6e6f7773d8c8d81863426f62") 217 | let decodedCBOR = try! CBOR(bytes) 218 | XCTAssertEqual(envelope, decodedCBOR) 219 | } 220 | 221 | func testTransforms() { 222 | var map = Map() 223 | map.insert(2.0, "Hi there!") 224 | map.insert(1, [10.5,-2.0]) 225 | runTest(map, 226 | #"map({0x01: (unsigned(1), array([simple(10.5), simple(-2.0)])), 0xf94000: (simple(2.0), text("Hi there!"))})"#, 227 | #"{1: [10.5, -2.0], 2.0: "Hi there!"}"#, 228 | "a20182f94940f9c000f9400069486920746865726521") 229 | let _ = map.remove(2.0) 230 | runTest(map, 231 | #"map({0x01: (unsigned(1), array([simple(10.5), simple(-2.0)]))})"#, 232 | #"{1: [10.5, -2.0]}"#, 233 | "a10182f94940f9c000") 234 | 235 | } 236 | 237 | func testFloat() throws { 238 | // Floating point numbers get serialized as their shortest accurate representation. 239 | runTest(1.5, "simple(1.5)", "1.5", "f93e00") 240 | runTest(2345678.25, "simple(2345678.25)", "2345678.25", "fa4a0f2b39") 241 | runTest(1.2, "simple(1.2)", "1.2", "fb3ff3333333333333") 242 | 243 | // Floating point values that can be represented as integers get serialized as integers. 244 | // AR: CDE 245 | runTest(Float(42.0), "simple(42.0)", "42.0", "f95140") 246 | runTest(2345678.0, "simple(2345678.0)", "2345678.0", "fa4a0f2b38") 247 | runTest(-2345678.0, "simple(-2345678.0)", "-2345678.0", "faca0f2b38") 248 | 249 | // Negative zero gets serialized as integer zero. 250 | // AR: CDE 251 | runTest(0.0, "simple(0.0)", "0.0", "f90000") 252 | runTest(-0.0, "simple(-0.0)", "-0.0", "f98000") 253 | 254 | // Smallest half-precision subnormal. 255 | runTest(5.960464477539063e-08, "simple(5.960464477539063e-08)", "5.960464477539063e-08", "f90001") 256 | 257 | // Smallest single subnormal. 258 | runTest(1.401298464324817e-45, "simple(1.401298464324817e-45)", "1.401298464324817e-45", "fa00000001") 259 | 260 | // Smallest double subnormal. 261 | runTest(5e-324, "simple(5e-324)", "5e-324", "fb0000000000000001") 262 | 263 | // Smallest double normal. 264 | runTest(2.2250738585072014e-308, "simple(2.2250738585072014e-308)", "2.2250738585072014e-308", "fb0010000000000000") 265 | 266 | // Smallest half-precision normal. 267 | runTest(6.103515625e-05, "simple(6.103515625e-05)", "6.103515625e-05", "f90400") 268 | 269 | // Largest possible half-precision. 270 | // AR: CDE 271 | runTest(65504.0, "simple(65504.0)", "65504.0", "f97bff") 272 | 273 | // Exponent 24 to test single exponent boundary. 274 | // AR: CDE 275 | runTest(33554430.0, "simple(33554430.0)", "33554430.0", "fa4bffffff") 276 | 277 | // Largest Double that also could be expressed as an integer. 278 | // AR: CDE 279 | runTest(9223372036854775000.0, "simple(9.223372036854775e+18)", "9.223372036854775e+18", "fb43dfffffffffffff") 280 | /* 281 | // Most negative double that converts to int64. 282 | runTest(-9223372036854774784.0, "negative(-9223372036854774784)", "-9223372036854774784", "3b7ffffffffffffbff") 283 | 284 | // Largest double that can convert to uint64, almost UINT64_MAX. 285 | runTest(18446744073709550000.0, "unsigned(18446744073709549568)", "18446744073709549568", "1bfffffffffffff800") 286 | 287 | // Just too large to convert to uint64, but converts to a single, just over UINT64_MAX. 288 | runTest(18446744073709552000.0, "simple(1.8446744073709552e+19)", "1.8446744073709552e+19", "fa5f800000") 289 | 290 | // Large negative that converts to negative int. 291 | runTest(-18446742974197924000.0, "negative(-18446742974197923840)", "-18446742974197923840", "3bfffffeffffffffff") 292 | */ 293 | // Largest possible single. 294 | runTest(3.4028234663852886e+38, "simple(3.4028234663852886e+38)", "3.4028234663852886e+38", "fa7f7fffff") 295 | 296 | // Slightly larger than largest possible single. 297 | runTest(3.402823466385289e+38, "simple(3.402823466385289e+38)", "3.402823466385289e+38", "fb47efffffe0000001") 298 | 299 | // Largest double. 300 | runTest(1.7976931348623157e+308, "simple(1.7976931348623157e+308)", "1.7976931348623157e+308", "fb7fefffffffffffff") 301 | } 302 | 303 | /* 304 | // AR: CDE 305 | func testIntCoercedToFloat() throws { 306 | let n = 42 307 | let c = n.cbor 308 | let f = try Double(cbor: c) 309 | XCTAssertEqual(f, Double(n)) 310 | let c2 = f.cbor 311 | XCTAssertEqual(c2, c) 312 | let i = try Int(cbor: c2) 313 | XCTAssertEqual(i, n) 314 | } 315 | */ 316 | 317 | func testFailFloatCoercedToInt() throws { 318 | // Floating point values cannot be coerced to integer types. 319 | let n = 42.5 320 | let c = n.cbor 321 | let f = try Double(cbor: c) 322 | XCTAssertEqual(f, n) 323 | XCTAssertThrowsError(try Int(cbor: c)) 324 | } 325 | 326 | func testNonCanonicalFloat1() throws { 327 | // Non-canonical representation of 1.5 that could be represented at a smaller width. 328 | XCTAssertThrowsError(try CBOR(‡"fb3ff8000000000000")) 329 | } 330 | /* 331 | // AR: CDE 332 | func testNonCanonicalFloat2() throws { 333 | // Non-canonical representation of a 12.0 value that could be represented as an integer. 334 | XCTAssertThrowsError(try CBOR(‡"f94a00")) 335 | } 336 | */ 337 | 338 | let canonicalNaNData = ‡"f97e00" 339 | let canonicalInfinityData = ‡"f97c00" 340 | let canonicalNegativeInfinityData = ‡"f9fc00" 341 | 342 | func testEncodeNaN() throws { 343 | let nonstandardDoubleNaN = Double(bitPattern: 0x7ff9100000000001) 344 | XCTAssert(nonstandardDoubleNaN.isNaN) 345 | XCTAssertEqual(nonstandardDoubleNaN.cborData, canonicalNaNData) 346 | 347 | let nonstandardFloatNaN = Float(bitPattern: 0xffc00001) 348 | XCTAssert(nonstandardFloatNaN.isNaN) 349 | XCTAssertEqual(nonstandardFloatNaN.cborData, canonicalNaNData) 350 | 351 | let nonstandardFloat16NaN = CBORFloat16(bitPattern: 0x7e01) 352 | XCTAssert(nonstandardFloat16NaN.isNaN) 353 | XCTAssertEqual(nonstandardFloat16NaN.cborData, canonicalNaNData) 354 | } 355 | 356 | func testDecodeNaN() throws { 357 | // Canonical NaN decodes 358 | XCTAssert(try Double(cbor: CBOR(canonicalNaNData)).isNaN) 359 | // Non-canonical NaNs of any size throw 360 | XCTAssertThrowsError(try CBOR(‡"f97e01")) 361 | XCTAssertThrowsError(try CBOR(‡"faffc00001")) 362 | XCTAssertThrowsError(try CBOR(‡"fb7ff9100000000001")) 363 | } 364 | 365 | func testEncodeInfinity() throws { 366 | XCTAssertEqual(Double.infinity.cborData, canonicalInfinityData) 367 | XCTAssertEqual(Float.infinity.cborData, canonicalInfinityData) 368 | XCTAssertEqual(CBORFloat16.infinity.cborData, canonicalInfinityData) 369 | XCTAssertEqual((-Double.infinity).cborData, canonicalNegativeInfinityData) 370 | XCTAssertEqual((-Float.infinity).cborData, canonicalNegativeInfinityData) 371 | XCTAssertEqual((-CBORFloat16.infinity).cborData, canonicalNegativeInfinityData) 372 | } 373 | 374 | func testDecodeInfinity() throws { 375 | // Canonical infinity decodes 376 | XCTAssert(try Double(cbor: CBOR(canonicalInfinityData)) == Double.infinity) 377 | XCTAssert(try Double(cbor: CBOR(canonicalNegativeInfinityData)) == -Double.infinity) 378 | 379 | // Non-canonical +infinities throw 380 | XCTAssertThrowsError(try CBOR(‡"fa7f800000")) 381 | XCTAssertThrowsError(try CBOR(‡"fb7ff0000000000000")) 382 | 383 | // Non-canonical -infinities throw 384 | XCTAssertThrowsError(try CBOR(‡"faff800000")) 385 | XCTAssertThrowsError(try CBOR(‡"fbfff0000000000000")) 386 | } 387 | 388 | } 389 | -------------------------------------------------------------------------------- /Tests/DCBORTests/FormatTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | import WolfBase 3 | import DCBOR 4 | 5 | let knownTags = TagsStore([Tag(1, "date")]) 6 | 7 | final class FormatTests: XCTestCase { 8 | func literal(_ c: CBOR) -> CBOR { c } 9 | 10 | func run(_ cbor: [CBOR], description: String? = nil, debugDescription: String? = nil, diagnostic: String? = nil, diagnosticAnnotated: String? = nil, dump: String? = nil, dumpAnnotated: String? = nil) { 11 | XCTAssert(cbor.dropFirst().allSatisfy { $0 == cbor.first }) 12 | let c = cbor.first! 13 | if let description { 14 | XCTAssertEqual(c.description, description) 15 | } 16 | if let debugDescription { 17 | XCTAssertEqual(c.debugDescription, debugDescription) 18 | } 19 | if let diagnostic { 20 | XCTAssertEqual(c.diagnostic(annotate: false), diagnostic) 21 | } 22 | if let diagnosticAnnotated { 23 | XCTAssertEqual(c.diagnostic(annotate: true, tags: knownTags), diagnosticAnnotated) 24 | } 25 | if let dump { 26 | XCTAssertEqual(c.hex(annotate: false), dump) 27 | } 28 | if let dumpAnnotated { 29 | XCTAssertEqual(c.hex(annotate: true, tags: knownTags), dumpAnnotated) 30 | } 31 | } 32 | 33 | func testFormatSimple() { 34 | run([CBOR(false), CBOR.false, false.cbor], 35 | description: "false", 36 | debugDescription:"simple(false)", 37 | diagnostic: "false", 38 | dump: "f4", 39 | dumpAnnotated: "f4 # false" 40 | ) 41 | run([CBOR(true), CBOR.true, true.cbor], 42 | description: "true", 43 | debugDescription: "simple(true)", 44 | diagnostic: "true", 45 | dump: "f5", 46 | dumpAnnotated: "f5 # true" 47 | ) 48 | run([CBOR(nil), CBOR.null, literal(nil)], 49 | description: "null", 50 | debugDescription: "simple(null)", 51 | diagnostic: "null", 52 | dump: "f6", 53 | dumpAnnotated: "f6 # null" 54 | ) 55 | // run([Simple(100).cbor], 56 | // description: "simple(100)", 57 | // debugDescription:"simple(100)", 58 | // diagnostic: "simple(100)", 59 | // dump: "f864", 60 | // dumpAnnotated: "f864 # simple(100)" 61 | // ) 62 | } 63 | 64 | func testFormatUnsigned() { 65 | run([CBOR(0), 0.cbor, literal(0)], 66 | description: "0", 67 | debugDescription: "unsigned(0)", 68 | diagnostic: "0", 69 | dump: "00", 70 | dumpAnnotated: "00 # unsigned(0)" 71 | ) 72 | 73 | run([CBOR(23), 23.cbor, literal(23)], 74 | description: "23", 75 | debugDescription: "unsigned(23)", 76 | diagnostic: "23", 77 | dump: "17", 78 | dumpAnnotated: "17 # unsigned(23)" 79 | ) 80 | 81 | run([CBOR(65546), 65546.cbor, literal(65546)], 82 | description: "65546", 83 | debugDescription: "unsigned(65546)", 84 | diagnostic: "65546", 85 | dump: "1a0001000a", 86 | dumpAnnotated: "1a0001000a # unsigned(65546)" 87 | ) 88 | 89 | run([CBOR(1000000000), 1000000000.cbor, literal(1000000000)], 90 | description: "1000000000", 91 | debugDescription: "unsigned(1000000000)", 92 | diagnostic: "1000000000", 93 | dump: "1a3b9aca00", 94 | dumpAnnotated: "1a3b9aca00 # unsigned(1000000000)" 95 | ) 96 | } 97 | 98 | func testFormatNegative() { 99 | run([CBOR(-1), (-1).cbor, literal(-1)], 100 | description: "-1", 101 | debugDescription: "negative(-1)", 102 | diagnostic: "-1", 103 | dump: "20", 104 | dumpAnnotated: "20 # negative(-1)" 105 | ) 106 | 107 | run([CBOR(-1000), (-1000).cbor, literal(-1000)], 108 | description: "-1000", 109 | debugDescription: "negative(-1000)", 110 | diagnostic: "-1000", 111 | dump: "3903e7", 112 | dumpAnnotated: "3903e7 # negative(-1000)" 113 | ) 114 | 115 | run([CBOR(-1000000), (-1000000).cbor, literal(-1000000)], 116 | description: "-1000000", 117 | debugDescription: "negative(-1000000)", 118 | diagnostic: "-1000000", 119 | dump: "3a000f423f", 120 | dumpAnnotated: "3a000f423f # negative(-1000000)" 121 | ) 122 | } 123 | 124 | func testFormatString() { 125 | run([CBOR("Test"), "Test".cbor, literal("Test")], 126 | description: #""Test""#, 127 | debugDescription: #"text("Test")"#, 128 | diagnostic: #""Test""#, 129 | dump: "6454657374", 130 | dumpAnnotated: """ 131 | 64 # text(4) 132 | 54657374 # "Test" 133 | """ 134 | ) 135 | } 136 | 137 | func testFormatSimpleArray() { 138 | run([CBOR([1, 2, 3]), [1, 2, 3].cbor, literal([1, 2, 3])], 139 | description: "[1, 2, 3]", 140 | debugDescription: "array([unsigned(1), unsigned(2), unsigned(3)])", 141 | diagnostic: "[1, 2, 3]", 142 | dump: "83010203", 143 | dumpAnnotated: """ 144 | 83 # array(3) 145 | 01 # unsigned(1) 146 | 02 # unsigned(2) 147 | 03 # unsigned(3) 148 | """ 149 | ) 150 | } 151 | 152 | func testFormatNestedArray() { 153 | let array: CBOR = [[1, 2, 3], ["A", "B", "C"]] 154 | run([array, literal([[1, 2, 3], ["A", "B", "C"]])], 155 | description: #"[[1, 2, 3], ["A", "B", "C"]]"#, 156 | debugDescription: #"array([array([unsigned(1), unsigned(2), unsigned(3)]), array([text("A"), text("B"), text("C")])])"#, 157 | diagnostic: """ 158 | [ 159 | [1, 2, 3], 160 | ["A", "B", "C"] 161 | ] 162 | """, 163 | dump: "828301020383614161426143", 164 | dumpAnnotated: """ 165 | 82 # array(2) 166 | 83 # array(3) 167 | 01 # unsigned(1) 168 | 02 # unsigned(2) 169 | 03 # unsigned(3) 170 | 83 # array(3) 171 | 61 # text(1) 172 | 41 # "A" 173 | 61 # text(1) 174 | 42 # "B" 175 | 61 # text(1) 176 | 43 # "C" 177 | """ 178 | ) 179 | } 180 | 181 | func testFormatMap() throws { 182 | var map: Map = [1: "A"] 183 | map.insert(2, "B") 184 | run([map.cbor], 185 | description: #"{1: "A", 2: "B"}"#, 186 | debugDescription: #"map({0x01: (unsigned(1), text("A")), 0x02: (unsigned(2), text("B"))})"#, 187 | diagnostic: #"{1: "A", 2: "B"}"#, 188 | dump: "a2016141026142", 189 | dumpAnnotated: """ 190 | a2 # map(2) 191 | 01 # unsigned(1) 192 | 61 # text(1) 193 | 41 # "A" 194 | 02 # unsigned(2) 195 | 61 # text(1) 196 | 42 # "B" 197 | """ 198 | ) 199 | } 200 | 201 | func testFormatTagged() { 202 | run([CBOR.tagged(100, "Hello")], 203 | description: #"100("Hello")"#, 204 | debugDescription: #"tagged(100, text("Hello"))"#, 205 | diagnostic: #"100("Hello")"#, 206 | dump: "d8646548656c6c6f", 207 | dumpAnnotated: """ 208 | d8 64 # tag(100) 209 | 65 # text(5) 210 | 48656c6c6f # "Hello" 211 | """ 212 | ) 213 | } 214 | 215 | func testFormatDate() { 216 | run([CBOR(Date(timeIntervalSince1970: -100))], 217 | description: "1(-100)", 218 | debugDescription: "tagged(1, negative(-100))", 219 | diagnostic: "1(-100)", 220 | diagnosticAnnotated: "1(1969-12-31T23:58:20Z) / date /", 221 | dump: "c13863", 222 | dumpAnnotated: """ 223 | c1 # tag(1) date 224 | 3863 # negative(-100) 225 | """ 226 | ) 227 | 228 | run([CBOR(Date(timeIntervalSince1970: 1647887071))], 229 | description: "1(1647887071)", 230 | debugDescription: "tagged(1, unsigned(1647887071))", 231 | diagnostic: "1(1647887071)", 232 | diagnosticAnnotated: "1(2022-03-21T18:24:31Z) / date /", 233 | dump: "c11a6238c2df", 234 | dumpAnnotated: """ 235 | c1 # tag(1) date 236 | 1a6238c2df # unsigned(1647887071) 237 | """ 238 | ) 239 | 240 | run([CBOR(Date(timeIntervalSince1970: 0))], 241 | description: "1(0)", 242 | debugDescription: "tagged(1, unsigned(0))", 243 | diagnostic: "1(0)", 244 | diagnosticAnnotated: "1(1970-01-01T00:00:00Z) / date /", 245 | dump: "c100", 246 | dumpAnnotated: """ 247 | c1 # tag(1) date 248 | 00 # unsigned(0) 249 | """ 250 | ) 251 | } 252 | 253 | func testFormatFractionalDate() { 254 | run([CBOR(Date(timeIntervalSince1970: 0.5))], 255 | description: "1(0.5)", 256 | debugDescription: "tagged(1, simple(0.5))", 257 | diagnostic: "1(0.5)", 258 | diagnosticAnnotated: "1(1970-01-01T00:00:00Z) / date /", 259 | dump: "c1f93800", 260 | dumpAnnotated: """ 261 | c1 # tag(1) date 262 | f93800 # 0.5 263 | """ 264 | ) 265 | } 266 | 267 | func testFormatStructure() throws { 268 | let encodedCBOR = ‡"d83183015829536f6d65206d7973746572696573206172656e2774206d65616e7420746f20626520736f6c7665642e82d902c3820158402b9238e19eafbc154b49ec89edd4e0fb1368e97332c6913b4beb637d1875824f3e43bd7fb0c41fb574f08ce00247413d3ce2d9466e0ccfa4a89b92504982710ad902c3820158400f9c7af36804ffe5313c00115e5a31aa56814abaa77ff301da53d48613496e9c51a98b36d55f6fb5634fdb0123910cfa4904f1c60523df41013dc3749b377900" 269 | let diagnostic = """ 270 | 49( 271 | [ 272 | 1, 273 | h'536f6d65206d7973746572696573206172656e2774206d65616e7420746f20626520736f6c7665642e', 274 | [ 275 | 707( 276 | [ 277 | 1, 278 | h'2b9238e19eafbc154b49ec89edd4e0fb1368e97332c6913b4beb637d1875824f3e43bd7fb0c41fb574f08ce00247413d3ce2d9466e0ccfa4a89b92504982710a' 279 | ] 280 | ), 281 | 707( 282 | [ 283 | 1, 284 | h'0f9c7af36804ffe5313c00115e5a31aa56814abaa77ff301da53d48613496e9c51a98b36d55f6fb5634fdb0123910cfa4904f1c60523df41013dc3749b377900' 285 | ] 286 | ) 287 | ] 288 | ] 289 | ) 290 | """ 291 | 292 | run([try CBOR(encodedCBOR)], 293 | description: "49([1, h'536f6d65206d7973746572696573206172656e2774206d65616e7420746f20626520736f6c7665642e', [707([1, h'2b9238e19eafbc154b49ec89edd4e0fb1368e97332c6913b4beb637d1875824f3e43bd7fb0c41fb574f08ce00247413d3ce2d9466e0ccfa4a89b92504982710a']), 707([1, h'0f9c7af36804ffe5313c00115e5a31aa56814abaa77ff301da53d48613496e9c51a98b36d55f6fb5634fdb0123910cfa4904f1c60523df41013dc3749b377900'])]])", 294 | debugDescription: "tagged(49, array([unsigned(1), bytes(536f6d65206d7973746572696573206172656e2774206d65616e7420746f20626520736f6c7665642e), array([tagged(707, array([unsigned(1), bytes(2b9238e19eafbc154b49ec89edd4e0fb1368e97332c6913b4beb637d1875824f3e43bd7fb0c41fb574f08ce00247413d3ce2d9466e0ccfa4a89b92504982710a)])), tagged(707, array([unsigned(1), bytes(0f9c7af36804ffe5313c00115e5a31aa56814abaa77ff301da53d48613496e9c51a98b36d55f6fb5634fdb0123910cfa4904f1c60523df41013dc3749b377900)]))])]))", 295 | diagnostic: diagnostic, 296 | diagnosticAnnotated: diagnostic, 297 | dump: encodedCBOR.hex, 298 | dumpAnnotated: """ 299 | d8 31 # tag(49) 300 | 83 # array(3) 301 | 01 # unsigned(1) 302 | 5829 # bytes(41) 303 | 536f6d65206d7973746572696573206172656e2774206d65616e7420746f20626520736f6c7665642e # "Some mysteries aren't meant to be solved." 304 | 82 # array(2) 305 | d9 02c3 # tag(707) 306 | 82 # array(2) 307 | 01 # unsigned(1) 308 | 5840 # bytes(64) 309 | 2b9238e19eafbc154b49ec89edd4e0fb1368e97332c6913b4beb637d1875824f3e43bd7fb0c41fb574f08ce00247413d3ce2d9466e0ccfa4a89b92504982710a 310 | d9 02c3 # tag(707) 311 | 82 # array(2) 312 | 01 # unsigned(1) 313 | 5840 # bytes(64) 314 | 0f9c7af36804ffe5313c00115e5a31aa56814abaa77ff301da53d48613496e9c51a98b36d55f6fb5634fdb0123910cfa4904f1c60523df41013dc3749b377900 315 | """ 316 | ) 317 | } 318 | 319 | func testFormatStructure2() throws { 320 | let encodedCBOR = ‡"d9012ca4015059f2293a5bce7d4de59e71b4207ac5d202c11a6035970003754461726b20507572706c652041717561204c6f766504787b4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f726520657420646f6c6f7265206d61676e6120616c697175612e" 321 | let diagnosticAnnotated = """ 322 | 300( 323 | { 324 | 1: 325 | h'59f2293a5bce7d4de59e71b4207ac5d2', 326 | 2: 327 | 1(2021-02-24T00:00:00Z), / date / 328 | 3: 329 | "Dark Purple Aqua Love", 330 | 4: 331 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 332 | } 333 | ) 334 | """ 335 | let diagnostic = """ 336 | 300( 337 | { 338 | 1: 339 | h'59f2293a5bce7d4de59e71b4207ac5d2', 340 | 2: 341 | 1(1614124800), 342 | 3: 343 | "Dark Purple Aqua Love", 344 | 4: 345 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 346 | } 347 | ) 348 | """ 349 | run([try CBOR(encodedCBOR)], 350 | description: #"300({1: h'59f2293a5bce7d4de59e71b4207ac5d2', 2: 1(1614124800), 3: "Dark Purple Aqua Love", 4: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."})"#, 351 | debugDescription: #"tagged(300, map({0x01: (unsigned(1), bytes(59f2293a5bce7d4de59e71b4207ac5d2)), 0x02: (unsigned(2), tagged(1, unsigned(1614124800))), 0x03: (unsigned(3), text("Dark Purple Aqua Love")), 0x04: (unsigned(4), text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."))}))"#, 352 | diagnostic: diagnostic, 353 | diagnosticAnnotated: diagnosticAnnotated, 354 | dump: encodedCBOR.hex, 355 | dumpAnnotated: """ 356 | d9 012c # tag(300) 357 | a4 # map(4) 358 | 01 # unsigned(1) 359 | 50 # bytes(16) 360 | 59f2293a5bce7d4de59e71b4207ac5d2 361 | 02 # unsigned(2) 362 | c1 # tag(1) date 363 | 1a60359700 # unsigned(1614124800) 364 | 03 # unsigned(3) 365 | 75 # text(21) 366 | 4461726b20507572706c652041717561204c6f7665 # "Dark Purple Aqua Love" 367 | 04 # unsigned(4) 368 | 78 7b # text(123) 369 | 4c6f72656d20697073756d20646f6c6f722073697420616d65742c20636f6e73656374657475722061646970697363696e6720656c69742c2073656420646f20656975736d6f642074656d706f7220696e6369646964756e74207574206c61626f726520657420646f6c6f7265206d61676e6120616c697175612e # "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." 370 | """ 371 | ) 372 | } 373 | 374 | /// Ensure that key order conforms to: 375 | /// https://www.rfc-editor.org/rfc/rfc8949.html#section-4.2.1-2.3.2.1 376 | func testFormatKeyOrder() throws { 377 | var map = Map() 378 | map[-1] = 3 379 | map[[-1]] = 7 380 | map["z"] = 4 381 | map[10] = 1 382 | map[false] = 8 383 | map[100] = 2 384 | map["aa"] = 5 385 | map[[100]] = 6 386 | let diagnostic = """ 387 | { 388 | 10: 389 | 1, 390 | 100: 391 | 2, 392 | -1: 393 | 3, 394 | "z": 395 | 4, 396 | "aa": 397 | 5, 398 | [100]: 399 | 6, 400 | [-1]: 401 | 7, 402 | false: 403 | 8 404 | } 405 | """ 406 | run([CBOR(map)], 407 | description: #"{10: 1, 100: 2, -1: 3, "z": 4, "aa": 5, [100]: 6, [-1]: 7, false: 8}"#, 408 | debugDescription: #"map({0x0a: (unsigned(10), unsigned(1)), 0x1864: (unsigned(100), unsigned(2)), 0x20: (negative(-1), unsigned(3)), 0x617a: (text("z"), unsigned(4)), 0x626161: (text("aa"), unsigned(5)), 0x811864: (array([unsigned(100)]), unsigned(6)), 0x8120: (array([negative(-1)]), unsigned(7)), 0xf4: (simple(false), unsigned(8))})"#, 409 | diagnostic: diagnostic, 410 | dump: "a80a011864022003617a046261610581186406812007f408", 411 | dumpAnnotated: """ 412 | a8 # map(8) 413 | 0a # unsigned(10) 414 | 01 # unsigned(1) 415 | 1864 # unsigned(100) 416 | 02 # unsigned(2) 417 | 20 # negative(-1) 418 | 03 # unsigned(3) 419 | 61 # text(1) 420 | 7a # "z" 421 | 04 # unsigned(4) 422 | 62 # text(2) 423 | 6161 # "aa" 424 | 05 # unsigned(5) 425 | 81 # array(1) 426 | 1864 # unsigned(100) 427 | 06 # unsigned(6) 428 | 81 # array(1) 429 | 20 # negative(-1) 430 | 07 # unsigned(7) 431 | f4 # false 432 | 08 # unsigned(8) 433 | """ 434 | ) 435 | } 436 | } 437 | --------------------------------------------------------------------------------