diff --git a/.swiftlint.yml b/.swiftlint.yml index 71715fe9f..5e178accf 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -38,6 +38,10 @@ line_length: - 150 - 175 +function_parameter_count: +- 8 +- 11 + function_body_length: - 300 - 350 diff --git a/Sources/PotentASN1/ASN1.swift b/Sources/PotentASN1/ASN1.swift index d0031a782..557038436 100644 --- a/Sources/PotentASN1/ASN1.swift +++ b/Sources/PotentASN1/ASN1.swift @@ -60,7 +60,8 @@ public indirect enum ASN1 { case characterString = 29 case bmpString = 30 - /// Creates an ASN.1 tag from the tag code (``ASN1/AnyTag-swift.typealias``), ``Class``, and if the tag is constructed. + /// Creates an ASN.1 tag from the tag code (``ASN1/AnyTag-swift.typealias``), ``Class``, + /// and if the tag is constructed. public static func tag(from value: UInt8, in tagClass: Class, constructed: Bool) -> AnyTag { return (value & ~0xE0) | tagClass.rawValue | (constructed ? 0x20 : 0x00) } @@ -335,10 +336,13 @@ public indirect enum ASN1 { /// Tag code for this ASN.1 value. /// public var anyTag: AnyTag { - guard case .tagged(let tag, _) = self else { - return knownTag!.universal + if let universal = knownTag?.universal { + return universal } - return tag + if case .tagged(let tag, _) = self { + return tag + } + fatalError() } /// Name of the tag for this value in ASN.1 notation. @@ -533,14 +537,12 @@ extension ASN1: Decodable { self = .ia5String(try container.decode(String.self)) case .utcTime: guard let time = ZonedDate(iso8601Encoded: try container.decode(String.self)) else { - throw DecodingError.dataCorruptedError(in: container, - debugDescription: "Invalid ISO8601 formatted date") + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid ISO8601 formatted date") } self = .utcTime(time) case .generalizedTime: guard let time = ZonedDate(iso8601Encoded: try container.decode(String.self)) else { - throw DecodingError.dataCorruptedError(in: container, - debugDescription: "Invalid ISO8601 formatted date") + throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid ISO8601 formatted date") } self = .generalizedTime(time) case .graphicString: @@ -640,19 +642,28 @@ extension ASN1: Encodable { try container.encode(anyTag) try container.encode(value) case .tagged(let tagValue, let data): - if let tag = Tag(rawValue: tagValue) { - let value = try ASN1DERReader.parseItem(data, as: tag.rawValue) - try value.encode(to: encoder) - } - else { - try container.encode(tagValue) - try container.encode(data) - } + try encodeTagged(tagValue: tagValue, data: data, encoder: encoder, container: &container) case .default: break } } + private func encodeTagged( + tagValue: AnyTag, + data: Data, + encoder: Swift.Encoder, + container: inout UnkeyedEncodingContainer + ) throws { + if let tag = Tag(rawValue: tagValue) { + let value = try ASN1DERReader.parseItem(data, as: tag.rawValue) + try value.encode(to: encoder) + } + else { + try container.encode(tagValue) + try container.encode(data) + } + } + } diff --git a/Sources/PotentASN1/ASN1DERReader.swift b/Sources/PotentASN1/ASN1DERReader.swift index f5d446cbf..fa9d91649 100644 --- a/Sources/PotentASN1/ASN1DERReader.swift +++ b/Sources/PotentASN1/ASN1DERReader.swift @@ -123,7 +123,7 @@ public enum ASN1DERReader { return .set(try parseItems(&itemBuffer)) default: // Default to saving tagged version - return .tagged(tagValue, Data(itemBuffer.popAll())) + return .tagged(tagValue, Data(try itemBuffer.popAll())) } } @@ -132,19 +132,15 @@ public enum ASN1DERReader { return .boolean(try itemBuffer.pop() != 0) case .integer: - if itemBuffer.isEmpty { - return .integer(BigInt.zero) - } - let data = Data(itemBuffer.popAll()) - return .integer(BigInt(derEncoded: data)) + return .integer(try parseInt(&itemBuffer)) case .bitString: let unusedBits = try itemBuffer.pop() - let data = Data(itemBuffer.popAll()) + let data = Data(try itemBuffer.popAll()) return .bitString((data.count * 8) - Int(unusedBits), data) case .octetString: - return .octetString(Data(itemBuffer.popAll())) + return .octetString(Data(try itemBuffer.popAll())) case .null: return .null @@ -174,18 +170,10 @@ public enum ASN1DERReader { return .ia5String(try parseString(&itemBuffer, tag: tag, encoding: .ascii)) case .utcTime: - let string = try parseString(&itemBuffer, tag: tag, encoding: .ascii) - guard let zonedDate = utcFormatter.date(from: string) else { - throw Error.invalidUTCTime - } - return .utcTime(zonedDate) + return .utcTime(try parseTime(&itemBuffer, formatter: utcFormatter)) case .generalizedTime: - let string = try parseString(&itemBuffer, tag: tag, encoding: .ascii) - guard let zonedDate = generalizedFormatter.date(from: string) else { - throw Error.invalidGeneralizedTime - } - return .generalizedTime(zonedDate) + return .generalizedTime(try parseTime(&itemBuffer, formatter: generalizedFormatter)) case .graphicString: return .graphicString(try parseString(&itemBuffer, tag: tag, encoding: .ascii)) @@ -210,18 +198,42 @@ public enum ASN1DERReader { case .objectDescriptor, .external, .enumerated, .embedded, .relativeOID: // Default to saving tagged version - return .tagged(tag.rawValue, Data(itemBuffer.popAll())) + return .tagged(tag.rawValue, Data(try itemBuffer.popAll())) } } + private static func parseTime( + _ buffer: inout UnsafeBufferPointer, + formatter: SuffixedDateFormatter + ) throws -> ZonedDate { + + guard let string = String(data: Data(try buffer.popAll()), encoding: .ascii) else { + throw Error.invalidStringEncoding + } + + guard let zonedDate = formatter.date(from: string) else { + throw Error.invalidUTCTime + } + + return zonedDate + } + + private static func parseInt(_ buffer: inout UnsafeBufferPointer) throws -> BigInt { + if buffer.isEmpty { + return BigInt.zero + } + let data = Data(try buffer.popAll()) + return BigInt(derEncoded: data) + } + private static func parseReal(_ buffer: inout UnsafeBufferPointer) throws -> Decimal { let lead = try buffer.pop() if lead & 0x40 == 0x40 { return lead & 0x1 == 0 ? Decimal(Double.infinity) : Decimal(-Double.infinity) } else if lead & 0xC0 == 0 { - let bytes = buffer.popAll() + let bytes = try buffer.popAll() return Decimal(string: String(bytes: bytes, encoding: .ascii) ?? "") ?? .zero } else { @@ -236,7 +248,7 @@ public enum ASN1DERReader { characterSet: CharacterSet? = nil ) throws -> String { - guard let string = String(data: Data(buffer.popAll()), encoding: encoding) else { + guard let string = String(data: Data(try buffer.popAll()), encoding: encoding) else { throw Error.invalidStringEncoding } @@ -322,29 +334,32 @@ public enum ASN1DERReader { private extension UnsafeBufferPointer { - mutating func popAll() -> UnsafeRawBufferPointer { + mutating func popAll() throws -> UnsafeRawBufferPointer { + guard let baseAddress = baseAddress else { + throw ASN1DERReader.Error.unexpectedEOF + } let buffer = UnsafeRawBufferPointer(start: baseAddress, count: count) - self = UnsafeBufferPointer(start: baseAddress?.advanced(by: count), count: 0) + self = UnsafeBufferPointer(start: baseAddress.advanced(by: count), count: 0) return buffer } mutating func pop(count: Int = 0) throws -> UnsafeBufferPointer { - guard self.count >= count else { + guard let baseAddress = baseAddress, self.count >= count else { throw ASN1DERReader.Error.unexpectedEOF } let buffer = UnsafeBufferPointer(start: baseAddress, count: count) - self = UnsafeBufferPointer(start: baseAddress?.advanced(by: count), count: self.count - count) + self = UnsafeBufferPointer(start: baseAddress.advanced(by: count), count: self.count - count) return buffer } mutating func pop() throws -> Element { - guard self.count >= 1 else { + guard let baseAddress = baseAddress, self.count >= 1 else { throw ASN1DERReader.Error.unexpectedEOF } defer { - self = UnsafeBufferPointer(start: baseAddress?.advanced(by: 1), count: self.count - 1) + self = UnsafeBufferPointer(start: baseAddress.advanced(by: 1), count: self.count - 1) } - return baseAddress!.pointee + return baseAddress.pointee } } diff --git a/Sources/PotentASN1/ASN1DERWriter.swift b/Sources/PotentASN1/ASN1DERWriter.swift index d166d5c1e..d8c7437bb 100644 --- a/Sources/PotentASN1/ASN1DERWriter.swift +++ b/Sources/PotentASN1/ASN1DERWriter.swift @@ -20,6 +20,8 @@ public class ASN1DERWriter { public enum Error: Swift.Error { /// Encoded value length could not be stored. case lengthOverflow + case invalidObjectIdentifierLength + case invalidStringCharacters } /// Write a collection of ``ASN1`` values in ASN.1/DER format. @@ -54,7 +56,8 @@ public class ASN1DERWriter { data.append(byte) } - private func append(length value: Int) throws { + private func append(length: Int) throws { + let value = UInt(length) switch value { case 0x0000 ..< 0x0080: @@ -64,21 +67,26 @@ public class ASN1DERWriter { append(byte: 0x81) append(byte: UInt8(value & 0x00FF)) - case 0x0100 ..< 0x8000: + case 0x0000 ..< 0x010000: append(byte: 0x82) append(byte: UInt8((value & 0xFF00) >> 8)) append(byte: UInt8(value & 0xFF)) default: - let bytes = BigUInt(value).derEncoded() - guard let byteCount = UInt8(exactly: bytes.count), byteCount <= 127 else { - throw Error.lengthOverflow - } - append(byte: 0x80 & byteCount) - append(data: bytes) + // Use big to ensure compact representation + try append(length: BigUInt(value)) } } + private func append(length: BigUInt) throws { + let bytes = length.derEncoded() + guard let byteCount = Int8(exactly: bytes.count) else { + throw Error.lengthOverflow + } + append(byte: 0x80 | UInt8(byteCount)) + append(data: bytes) + } + public func append(data: Data) { self.data.append(data) } @@ -107,12 +115,7 @@ public class ASN1DERWriter { append(data: bytes.isEmpty ? Self.zero : bytes) case .bitString(let length, let data): - let usedBits = UInt8(length % 8) - let unusedBits = usedBits == 0 ? 0 : 8 - usedBits - - try append(tag: .bitString, length: data.count + 1) - append(byte: unusedBits) - append(data: data) + try writeBitString(length: length, data: data) case .octetString(let value): try append(tag: .octetString, length: value.count) @@ -121,53 +124,14 @@ public class ASN1DERWriter { case .null: try append(tag: .null, length: 0) - case .objectIdentifier(let value): - - func field(val: UInt64) -> Data { - var val = val - var result = Data(count: 9) - var pos = 8 - result[pos] = UInt8(val & 0x7F) - while val >= (UInt64(1) << 7) { - val >>= 7 - pos -= 1 - result[pos] = UInt8((val & 0x7F) | 0x80) - } - return Data(result.dropFirst(pos)) - } - - var iter = value.makeIterator() - - let first = iter.next()! - let second = iter.next()! - - var bytes = field(val: first * 40 + second) - - while let val = iter.next() { - bytes.append(field(val: val)) - } - - try append(tag: .objectIdentifier, length: bytes.count) - append(data: bytes) + case .objectIdentifier(let fields): + try writeObjectIdentifier(fields: fields) case .real(let value): - guard !value.isZero else { - try append(tag: .real, length: 0) - return - } - guard !value.isInfinite else { - try append(tag: .real, length: 1) - append(byte: 0x40 | (value.sign == .plus ? 0x0 : 0x1)) - return - } - // Choose ISO-6093 NR3 - var data = String(describing: value).data(using: .ascii) ?? Data() - data.insert(0x00, at: 0) - try append(tag: .real, length: data.count) - append(data: data) + try writeReal(value) case .utf8String(let value): - let utf8 = value.data(using: String.Encoding.utf8)! + let utf8 = try data(forString: value, encoding: .utf8) try append(tag: .utf8String, length: utf8.count) append(data: utf8) @@ -182,69 +146,69 @@ public class ASN1DERWriter { append(data: data) case .numericString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .numericString, length: ascii.count) append(data: ascii) case .printableString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .printableString, length: ascii.count) append(data: ascii) case .teletexString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .teletexString, length: ascii.count) append(data: ascii) case .videotexString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .videotexString, length: ascii.count) append(data: ascii) case .ia5String(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .ia5String, length: ascii.count) append(data: ascii) case .utcTime(let value): let formatter = UTCFormatters.for(timeZone: value.timeZone) - let ascii = formatter.string(from: value.date).data(using: .ascii)! + let ascii = try data(forString: formatter.string(from: value.date), encoding: .ascii) try append(tag: .utcTime, length: ascii.count) append(data: ascii) case .generalizedTime(let value): let formatter = GeneralizedFormatters.for(timeZone: value.timeZone) - let ascii = formatter.string(from: value.date).data(using: .ascii)! + let ascii = try data(forString: formatter.string(from: value.date), encoding: .ascii) try append(tag: .generalizedTime, length: ascii.count) append(data: ascii) case .graphicString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .graphicString, length: ascii.count) append(data: ascii) case .visibleString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .visibleString, length: ascii.count) append(data: ascii) case .generalString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .generalString, length: ascii.count) append(data: ascii) case .universalString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .universalString, length: ascii.count) append(data: ascii) case .characterString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .characterString, length: ascii.count) append(data: ascii) case .bmpString(let value): - let ascii = value.data(using: String.Encoding.ascii)! + let ascii = try data(forString: value, encoding: .ascii) try append(tag: .bmpString, length: ascii.count) append(data: ascii) @@ -258,6 +222,72 @@ public class ASN1DERWriter { } } + private func writeBitString(length: Int, data: Data) throws { + let usedBits = UInt8(length % 8) + let unusedBits = usedBits == 0 ? 0 : 8 - usedBits + + try append(tag: .bitString, length: data.count + 1) + append(byte: unusedBits) + append(data: data) + } + + private func writeObjectIdentifier(fields: ([UInt64])) throws { + + func field(val: UInt64) -> Data { + var val = val + var result = Data(count: 9) + var pos = 8 + result[pos] = UInt8(val & 0x7F) + while val >= (UInt64(1) << 7) { + val >>= 7 + pos -= 1 + result[pos] = UInt8((val & 0x7F) | 0x80) + } + return Data(result.dropFirst(pos)) + } + + var iter = fields.makeIterator() + + guard let first = iter.next(), let second = iter.next() else { + throw Error.invalidObjectIdentifierLength + } + + var bytes = field(val: first * 40 + second) + + while let val = iter.next() { + bytes.append(field(val: val)) + } + + try append(tag: .objectIdentifier, length: bytes.count) + append(data: bytes) + } + + private func writeReal(_ value: Decimal) throws { + guard !value.isZero else { + try append(tag: .real, length: 0) + return + } + + guard !value.isInfinite else { + try append(tag: .real, length: 1) + append(byte: 0x40 | (value.sign == .plus ? 0x0 : 0x1)) + return + } + + // Choose ISO-6093 NR3 + var data = String(describing: value).data(using: .ascii) ?? Data() + data.insert(0x00, at: 0) + try append(tag: .real, length: data.count) + append(data: data) + } + + private func data(forString string: String, encoding: String.Encoding) throws -> Data { + guard let data = string.data(using: encoding) else { + throw Error.invalidStringCharacters + } + return data + } + } private enum UTCFormatters { diff --git a/Sources/PotentASN1/ASN1Decoder.swift b/Sources/PotentASN1/ASN1Decoder.swift index c720243cc..f1d48456c 100644 --- a/Sources/PotentASN1/ASN1Decoder.swift +++ b/Sources/PotentASN1/ASN1Decoder.swift @@ -115,21 +115,25 @@ public struct ASN1DecoderTransform: InternalDecoderTransform, InternalValueDeser return try state.decode(value) } + static let interceptedTypes: [Any.Type] = [ + AnyString.self, + AnyTime.self, + ZonedDate.self, + BitString.self, + ObjectIdentifier.self, + ASN1.Integer.self, + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Decodable.Type) -> Bool { - if type as? Tagged.Type != nil { - return true - } - return type == AnyString.self || type == AnyTime.self - || type == BitString.self || type == ObjectIdentifier.self - || type == ASN1.Integer.self - || type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type == AnyValue.self + return interceptedTypes.contains { $0 == type } || type is Tagged.Type } public static func unbox(_ value: ASN1, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { diff --git a/Sources/PotentASN1/ASN1Encoder.swift b/Sources/PotentASN1/ASN1Encoder.swift index 779c18e28..13a75e0d1 100644 --- a/Sources/PotentASN1/ASN1Encoder.swift +++ b/Sources/PotentASN1/ASN1Encoder.swift @@ -109,21 +109,25 @@ public struct ASN1EncoderTransform: InternalEncoderTransform, InternalValueSeria return try state.encode(value) } + static let interceptedTypes: [Any.Type] = [ + AnyString.self, + AnyTime.self, + ZonedDate.self, + BitString.self, + ObjectIdentifier.self, + ASN1.Integer.self, + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Encodable.Type) -> Bool { - if type as? Tagged.Type != nil { - return true - } - return type == ASN1.self - || type == AnyString.self || type == AnyTime.self || type == ZonedDate.self - || type == BitString.self || type == ObjectIdentifier.self || type == ASN1.Integer.self - || type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type == AnyValue.self + return interceptedTypes.contains { $0 == type } || type is Tagged.Type } public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> ASN1 { diff --git a/Sources/PotentASN1/Dates.swift b/Sources/PotentASN1/Dates.swift index e5af27356..7a9ef4fc3 100644 --- a/Sources/PotentASN1/Dates.swift +++ b/Sources/PotentASN1/Dates.swift @@ -78,11 +78,11 @@ public struct SuffixedDateFormatter { else { parsedDate = noSuffixes.date(from: string) } - guard parsedDate != nil else { + guard let parsedDate = parsedDate else { return nil } let parsedTimeZone = TimeZone.timeZone(from: String(string[zoneStartIndex...])) ?? .current - return ZonedDate(date: parsedDate!, timeZone: parsedTimeZone) + return ZonedDate(date: parsedDate, timeZone: parsedTimeZone) } } diff --git a/Sources/PotentASN1/Schema.swift b/Sources/PotentASN1/Schema.swift index 83d0b1d21..7438bf689 100644 --- a/Sources/PotentASN1/Schema.swift +++ b/Sources/PotentASN1/Schema.swift @@ -104,43 +104,44 @@ public indirect enum Schema: Equatable, Hashable { public var possibleTags: [ASN1.AnyTag]? { switch self { - case .sequence, .sequenceOf: return [ASN1.Tag.sequence.universal] - case .setOf: return [ASN1.Tag.set.universal] - case .choiceOf(let schemas): return schemas.flatMap { $0.possibleTags ?? [] } - case .type(let schema): return schema.possibleTags - case .version(let schema): return schema.possibleTags - case .versioned(_, let schema): return schema.possibleTags - case .optional(let schema): return schema.possibleTags - case .implicit(let tag, let tagClass, let schema): return [ASN1.Tag.tag(from: tag, in: tagClass, constructed: schema.isCollection)] - case .explicit(let tag, let tagClass, _): return [ASN1.Tag.tag(from: tag, in: tagClass, constructed: true)] - case .boolean: return [ASN1.Tag.boolean.universal] - case .integer: return [ASN1.Tag.integer.universal] - case .real: return [ASN1.Tag.real.universal] - case .bitString: return [ASN1.Tag.bitString.universal] - case .octetString: return [ASN1.Tag.octetString.universal] - case .objectIdentifier: return [ASN1.Tag.objectIdentifier.universal] + case .sequence, .sequenceOf: + return [ASN1.Tag.sequence.universal] + case .setOf: + return [ASN1.Tag.set.universal] + case .choiceOf(let schemas): + return schemas.flatMap { $0.possibleTags ?? [] } + case .type(let schema): + return schema.possibleTags + case .version(let schema): + return schema.possibleTags + case .versioned(_, let schema): + return schema.possibleTags + case .optional(let schema): + return schema.possibleTags + case .implicit(let tag, let tagClass, let schema): + return [ASN1.Tag.tag(from: tag, in: tagClass, constructed: schema.isCollection)] + case .explicit(let tag, let tagClass, _): + return [ASN1.Tag.tag(from: tag, in: tagClass, constructed: true)] + case .boolean: + return [ASN1.Tag.boolean.universal] + case .integer: + return [ASN1.Tag.integer.universal] + case .real: + return [ASN1.Tag.real.universal] + case .bitString: + return [ASN1.Tag.bitString.universal] + case .octetString: + return [ASN1.Tag.octetString.universal] + case .objectIdentifier: + return [ASN1.Tag.objectIdentifier.universal] case .string(let kind, _): - switch kind { - case .utf8: return [ASN1.Tag.utf8String.universal] - case .numeric: return [ASN1.Tag.numericString.universal] - case .printable: return [ASN1.Tag.printableString.universal] - case .teletex: return [ASN1.Tag.teletexString.universal] - case .videotex: return [ASN1.Tag.videotexString.universal] - case .ia5: return [ASN1.Tag.ia5String.universal] - case .graphic: return [ASN1.Tag.graphicString.universal] - case .visible: return [ASN1.Tag.visibleString.universal] - case .general: return [ASN1.Tag.generalString.universal] - case .universal: return [ASN1.Tag.universalString.universal] - case .character: return [ASN1.Tag.characterString.universal] - case .bmp: return [ASN1.Tag.bmpString.universal] - } + return kind.possibleTags case .time(let kind): - switch kind { - case .utc: return [ASN1.Tag.utcTime.universal] - case .generalized: return [ASN1.Tag.generalizedTime.universal] - } - case .null: return [ASN1.Tag.null.universal] - case .any, .dynamic, .nothing: return nil + return kind.possibleTags + case .null: + return [ASN1.Tag.null.universal] + case .any, .dynamic, .nothing: + return nil } } @@ -265,164 +266,189 @@ public indirect enum Schema: Equatable, Hashable { extension Schema: CustomStringConvertible { public var description: String { - var result = "" - var indent = "" - - func append(_ value: String) { - result.append(value.replacingOccurrences(of: "\n", with: "\n\(indent)")) - } - + var output = FormattedOutput() switch self { case .nothing: break case .sequence(let fields): - indent += " " - defer { - indent.removeLast(2) - append("\n}") - } - - append("SEQUENCE ::= {") - - for (field, fieldSchema) in fields { - append("\n\(field) \(fieldSchema.description)") - } + sequenceDescription(&output, fields) case .sequenceOf(let schema, size: let size): - indent += " " - defer { - indent.removeLast(2) - } - - append("SEQUENCE ") - - if let size = size { - append("SIZE (\(size)) ") - } - - append("OF\n\(schema.description)") + sequenceOfDescription(&output, size, schema) case .setOf(let schema, size: let size): - indent += " " - defer { - indent.removeLast(2) - } - - append("SET ") - - if let size = size { - append("SIZE (\(size)) ") - } - - append("OF\n\(schema.description)") + setOfDescription(&output, size, schema) case .choiceOf(let schemas): - indent += " " - defer { - indent.removeLast(2) - append("\n}") - } - append("CHOICE ::= {") - - for schema in schemas { - append("\n\(schema.description)") - } + choiceOfDescription(&output, schemas) case .optional(let schema): - append("\(schema.description) OPTIONAL") + output.write("\(schema.description) OPTIONAL") case .implicit(let tag, let inClass, let schema): let tagPrefix = inClass != .contextSpecific ? String(describing: inClass).uppercased() : "" - append("[\(tagPrefix)\(tag)] IMPLICIT \(schema.description)") + output.write("[\(tagPrefix)\(tag)] IMPLICIT \(schema.description)") case .explicit(let tag, let inClass, let schema): let tagPrefix = inClass != .contextSpecific ? String(describing: inClass).uppercased() : "" - append("[\(tagPrefix)\(tag)] EXPLICIT \(schema.description)") + output.write("[\(tagPrefix)\(tag)] EXPLICIT \(schema.description)") case .boolean(let def): - append("BOOLEAN") + output.write("BOOLEAN") if let def = def { - append(" DEFAULT \(String(def).uppercased())") + output.write(" DEFAULT \(String(def).uppercased())") } case .integer(let allowed, let def): - append("INTEGER") - if let allowed = allowed { - append(" { \(allowed.map { String($0) }.joined(separator: ", ")) }") - } - if let def = def { - append(" DEFAULT \(String(def).uppercased())") - } + integerDescription(&output, allowed, def) case .bitString(let size): - append("BIT STRING") + output.write("BIT STRING") if let size = size { - append(" (\(size))") + output.write(" (\(size))") } case .octetString(let size): - append("OCTET STRING") + output.write("OCTET STRING") if let size = size { - append(" (\(size))") + output.write(" (\(size))") } case .objectIdentifier(let allowed): - append("OBJECT IDENTIFIER") + output.write("OBJECT IDENTIFIER") if let allowed = allowed { - append(" { \(allowed.map { $0.description }.sorted().joined(separator: " , ")) }") + output.write(" { \(allowed.map { $0.description }.sorted().joined(separator: " , ")) }") } case .real: - append("REAL") + output.write("REAL") case .string(let type, let size): - append("\(String(describing: type).uppercased())String") + output.write("\(String(describing: type).uppercased())String") if let size = size { - append(" (\(size))") + output.write(" (\(size))") } case .time(kind: let kind): - switch kind { - case .utc: - append("UTCTime") - case .generalized: - append("GeneralizedTime") - } + timeDescription(&output, kind) case .null: - append("NULL") + output.write("NULL") case .any: - append("ANY") + output.write("ANY") case .dynamic(let unknownSchema, let map): - indent += " " - defer { - indent.removeLast(2) - append("\n}") + dynamicDescription(&output, map, unknownSchema) + + case .versioned(let versionRange, let versionedSchema): + output.write( + "\(versionedSchema.description) -- for version (\(versionRange.lowerBound)..\(versionRange.upperBound))" + ) + + case .type(let schema), .version(let schema): + output.write(schema.description) + } + + return output.string + } + + private func sequenceDescription(_ output: inout FormattedOutput, _ fields: Schema.StructureSequenceMap) { + output.indented(prefix: "SEQUENCE ::= {", suffix: "\n}") { writer in + + for (field, fieldSchema) in fields { + writer("\n\(field) \(fieldSchema.description)") + } + + } + } + + private func sequenceOfDescription(_ output: inout FormattedOutput, _ size: Schema.Size?, _ schema: Schema) { + output.indented(prefix: "SEQUENCE ") { writer in + + if let size = size { + writer("SIZE (\(size)) ") + } + + writer("OF\n\(schema.description)") + } + } + + private func setOfDescription(_ output: inout FormattedOutput, _ size: Schema.Size?, _ schema: Schema) { + output.indented(prefix: "SET ") { writer in + + if let size = size { + writer("SIZE (\(size)) ") + } + + writer("OF\n\(schema.description)") + } + } + + private func choiceOfDescription(_ output: inout FormattedOutput, _ schemas: ([Schema])) { + output.indented(prefix: "CHOICE ::= {", suffix: "\n}") { writer in + + for schema in schemas { + writer("\n\(schema.description)") } + } + } + + private func integerDescription(_ output: inout FormattedOutput, _ allowed: Range?, _ def: BigInt?) { + output.write("INTEGER") + if let allowed = allowed { + output.write(" { \(allowed.map { String($0) }.joined(separator: ", ")) }") + } + if let def = def { + output.write(" DEFAULT \(String(def).uppercased())") + } + } + + private func timeDescription(_ output: inout FormattedOutput, _ kind: AnyTime.Kind) { + switch kind { + case .utc: + output.write("UTCTime") + case .generalized: + output.write("GeneralizedTime") + } + } - append("DYNAMIC {") + private func dynamicDescription(_ output: inout FormattedOutput, _ map: Schema.DynamicMap, _ unknownSchema: Schema?) { + output.indented(prefix: "DYNAMIC {", suffix: "\n}") { writer in for (key, schema) in map { - append("\nCASE \(key.unwrapped ?? key) (\(key.tagName)): \(schema.description)") + writer("\nCASE \(key.unwrapped ?? key) (\(key.tagName)): \(schema.description)") } if let unknownSchema = unknownSchema { - append("\nELSE: \(unknownSchema.description)") + writer("\nELSE: \(unknownSchema.description)") } + } + } - case .versioned(let versionRange, let versionedSchema): - append( - "\(versionedSchema.description) -- for version (\(versionRange.lowerBound)..\(versionRange.upperBound))" - ) +} - case .type(let schema), .version(let schema): - append(schema.description) +private struct FormattedOutput: TextOutputStream { + var string: String = "" + private var indent: String = "" + + mutating func write(_ string: String) { + self.string.append(string.replacingOccurrences(of: "\n", with: "\n\(indent)")) + } + + mutating func indented(prefix: String = "", suffix: String = "", _ block: ((String) -> Void) -> Void) { + write(prefix) + + let prevIndent = self.indent + self.indent.append(" ") + + defer { + self.indent = prevIndent + write(suffix) } - return result + block { write($0) } } } @@ -439,3 +465,35 @@ extension Schema.Size: CustomStringConvertible { } } + +extension AnyString.Kind { + + var possibleTags: [ASN1.AnyTag] { + switch self { + case .utf8: return [ASN1.Tag.utf8String.universal] + case .numeric: return [ASN1.Tag.numericString.universal] + case .printable: return [ASN1.Tag.printableString.universal] + case .teletex: return [ASN1.Tag.teletexString.universal] + case .videotex: return [ASN1.Tag.videotexString.universal] + case .ia5: return [ASN1.Tag.ia5String.universal] + case .graphic: return [ASN1.Tag.graphicString.universal] + case .visible: return [ASN1.Tag.visibleString.universal] + case .general: return [ASN1.Tag.generalString.universal] + case .universal: return [ASN1.Tag.universalString.universal] + case .character: return [ASN1.Tag.characterString.universal] + case .bmp: return [ASN1.Tag.bmpString.universal] + } + } + +} + +extension AnyTime.Kind { + + var possibleTags: [ASN1.AnyTag] { + switch self { + case .utc: return [ASN1.Tag.utcTime.universal] + case .generalized: return [ASN1.Tag.generalizedTime.universal] + } + } + +} diff --git a/Sources/PotentCBOR/CBORDecoder.swift b/Sources/PotentCBOR/CBORDecoder.swift index fa4d60182..8751579e3 100644 --- a/Sources/PotentCBOR/CBORDecoder.swift +++ b/Sources/PotentCBOR/CBORDecoder.swift @@ -69,16 +69,20 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser public let userInfo: [CodingUserInfoKey: Any] } + static let interceptedTypes: [Any.Type] = [ + CBOR.Half.self, + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Decodable.Type) -> Bool { - return type == CBOR.Half.self - || type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type == AnyValue.self + return interceptedTypes.contains { $0 == type } } public static func unbox(_ value: CBOR, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { @@ -187,6 +191,15 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser case .double(let dbl): return try coerce(dbl, at: decoder.codingPath) case .tagged(.decimalFraction, .array(let items)) where items.count == 2: + return try decode(fromDecimalFraction: items) + case .tagged(_, let untagged): + return try unbox(untagged, type: type, decoder: decoder) + case .null: return nil + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + func decode(fromDecimalFraction items: [CBOR]) throws -> T { guard let exp = try? unbox(items[0], type: Int.self, decoder: decoder), let man = try? unbox(items[1], type: BigInt.self, decoder: decoder) @@ -201,11 +214,6 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser throw overflow(type, value: value, at: decoder.codingPath) } return result - case .tagged(_, let untagged): - return try unbox(untagged, type: type, decoder: decoder) - case .null: return nil - default: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } @@ -234,6 +242,15 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser case .double(let dbl): return Decimal(dbl) case .tagged(.decimalFraction, .array(let items)) where items.count == 2: + return try decode(fromDecimalFraction: items) + case .tagged(_, let untagged): + return try unbox(untagged, type: type, decoder: decoder) + case .null: return nil + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + func decode(fromDecimalFraction items: [CBOR]) throws -> Decimal { guard let exp = try? unbox(items[0], type: Int.self, decoder: decoder), let man = try? unbox(items[1], type: BigInt.self, decoder: decoder) @@ -245,11 +262,6 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser throw overflow(Decimal.self, value: value, at: decoder.codingPath) } return Decimal(sign: man.sign == .plus ? .plus : .minus, exponent: exp, significand: sig) - case .tagged(_, let untagged): - return try unbox(untagged, type: type, decoder: decoder) - case .null: return nil - default: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } @@ -321,12 +333,24 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser public static func unbox(_ value: CBOR, as type: UUID.Type, decoder: IVD) throws -> UUID? { switch value { case .utf8String(let string), .tagged(.uuid, .utf8String(let string)): + return try decode(from: string) + case .byteString(let data), .tagged(.uuid, .byteString(let data)): + return try decode(from: data) + case .tagged(_, let tagged): return try unbox(tagged, as: type, decoder: decoder) + case .null: return nil + default: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + } + + func decode(from string: String) throws -> UUID { guard let result = UUID(uuidString: string) else { throw DecodingError.typeMismatch(type, .init(codingPath: decoder.codingPath, debugDescription: "Expected properly formatted UUID string")) } return result - case .byteString(let data), .tagged(.uuid, .byteString(let data)): + } + + func decode(from data: Data) throws -> UUID { guard data.count == MemoryLayout.size else { throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } @@ -335,10 +359,6 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser _ = data.copyBytes(to: ptr) } return UUID(uuid: uuid) - case .tagged(_, let tagged): return try unbox(tagged, as: type, decoder: decoder) - case .null: return nil - default: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } } @@ -358,33 +378,18 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser public static func unbox(_ value: CBOR, as type: Date.Type, decoder: IVD) throws -> Date? { - func decodeUntaggedNumericDate(from value: TimeInterval, unitsPerSeconds: Double) -> Date { - switch decoder.options.untaggedDateDecodingStrategy { - case .unitsSince1970: - return Date(timeIntervalSince1970: value / unitsPerSeconds) - case .millisecondsSince1970: - return Date(timeIntervalSince1970: value / 1000.0) - case .secondsSince1970: - return Date(timeIntervalSince1970: value) - } - } - switch value { case .utf8String(let string), .tagged(.iso8601DateTime, .utf8String(let string)): - guard let date = ZonedDate(iso8601Encoded: string) else { - throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, - debugDescription: "Expected date string to be ISO8601-formatted.")) - } - return date.utcDate + return try decode(from: string) case .tagged(.epochDateTime, let number): return try unbox(number, type: TimeInterval.self, decoder: decoder).map { Date(timeIntervalSince1970: $0) } case .unsignedInt, .negativeInt, .simple, .tagged(.positiveBignum, _), .tagged(.negativeBignum, _): return try unbox(value, type: Int64.self, decoder: decoder).map { - decodeUntaggedNumericDate(from: TimeInterval($0), unitsPerSeconds: 1000.0) + decode(fromUntaggedNumericDate: TimeInterval($0), unitsPerSeconds: 1000.0) } case .double, .float, .half, .tagged(.decimalFraction, _): return try unbox(value, type: TimeInterval.self, decoder: decoder).map { - decodeUntaggedNumericDate(from: $0, unitsPerSeconds: 1.0) + decode(fromUntaggedNumericDate: $0, unitsPerSeconds: 1.0) } case .tagged(_, let tagged): return try unbox(tagged, as: type, decoder: decoder) @@ -392,6 +397,25 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser default: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } + + func decode(from string: String) throws -> Date { + guard let date = ZonedDate(iso8601Encoded: string) else { + throw DecodingError.dataCorrupted(.init(codingPath: decoder.codingPath, + debugDescription: "Expected date string to be ISO8601-formatted.")) + } + return date.utcDate + } + + func decode(fromUntaggedNumericDate value: TimeInterval, unitsPerSeconds: Double) -> Date { + switch decoder.options.untaggedDateDecodingStrategy { + case .unitsSince1970: + return Date(timeIntervalSince1970: value / unitsPerSeconds) + case .millisecondsSince1970: + return Date(timeIntervalSince1970: value / 1000.0) + case .secondsSince1970: + return Date(timeIntervalSince1970: value) + } + } } public static func unbox(_ value: CBOR, as type: Data.Type, decoder: IVD) throws -> Data? { @@ -472,11 +496,7 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser case .array(let value): return .array(try value.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) case .map(let value): - return .dictionary(.init(uniqueKeysWithValues: try value.map { key, value in - let key = try unbox(key, as: AnyValue.self, decoder: decoder) - let value = try unbox(value, as: AnyValue.self, decoder: decoder) - return (key, value) - })) + return try dictionary(from: value) case .tagged(.positiveBignum, _), .tagged(.negativeBignum, _): guard let int = try unbox(value, as: BigInt.self, decoder: decoder) else { throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: BigInt.self, reality: value) @@ -501,6 +521,14 @@ public struct CBORDecoderTransform: InternalDecoderTransform, InternalValueDeser case .tagged(_, let untagged): return try unbox(untagged, as: AnyValue.self, decoder: decoder) } + + func dictionary(from value: CBOR.Map) throws -> AnyValue { + return .dictionary(.init(uniqueKeysWithValues: try value.map { key, value in + let key = try unbox(key, as: AnyValue.self, decoder: decoder) + let value = try unbox(value, as: AnyValue.self, decoder: decoder) + return (key, value) + })) + } } public static func valueToUnkeyedValues(_ value: CBOR, decoder: IVD) throws -> UnkeyedValues? { diff --git a/Sources/PotentCBOR/CBOREncoder.swift b/Sources/PotentCBOR/CBOREncoder.swift index 6d9035011..52b655550 100644 --- a/Sources/PotentCBOR/CBOREncoder.swift +++ b/Sources/PotentCBOR/CBOREncoder.swift @@ -67,16 +67,20 @@ public struct CBOREncoderTransform: InternalEncoderTransform, InternalValueSeria public let userInfo: [CodingUserInfoKey: Any] } + static let interceptedTypes: [Any.Type] = [ + CBOR.Half.self, + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Encodable.Type) -> Bool { - return type == CBOR.Half.self - || type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type == AnyValue.self + return interceptedTypes.contains { $0 == type } } public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> CBOR { @@ -146,7 +150,10 @@ public struct CBOREncoderTransform: InternalEncoderTransform, InternalValueSeria let exp: CBOR = value.exponent < 0 ? .negativeInt(UInt64(bitPattern: ~Int64(value.exponent))) : .unsignedInt(UInt64(value.exponent)) - let sig = BigInt(value.significand.description)! + guard let sig = BigInt(value.significand.description) else { + throw EncodingError.invalidValue(value, .init(codingPath: encoder.codingPath, + debugDescription: "Significand cannot be represented as integer")) + } let man: CBOR = value.sign == .plus ? .tagged(.positiveBignum, .byteString(sig.magnitude.serialize())) : .tagged(.negativeBignum, .byteString((sig.magnitude - 1).serialize())) @@ -222,6 +229,10 @@ public struct CBOREncoderTransform: InternalEncoderTransform, InternalValueSeria case .array(let value): return .array(.init(try value.map { try box($0, encoder: encoder) })) case .dictionary(let value): + return try encode(value) + } + + func encode(_ value: AnyValue.AnyDictionary) throws -> CBOR { return .map(.init(uniqueKeysWithValues: try value.map { key, value in let key = try box(key, encoder: encoder) let value = try box(value, encoder: encoder) diff --git a/Sources/PotentCBOR/CBORReader.swift b/Sources/PotentCBOR/CBORReader.swift index 66cc4c5db..dad3da8d3 100644 --- a/Sources/PotentCBOR/CBORReader.swift +++ b/Sources/PotentCBOR/CBORReader.swift @@ -158,27 +158,13 @@ public struct CBORReader { let numBytes = try readLength(initByte, base: 0x40) return .byteString(try stream.readBytes(count: numBytes)) case 0x5F: - let values = try decodeItemsUntilBreak().map { cbor -> Data in - guard case .byteString(let bytes) = cbor else { throw CBORError.invalidIndefiniteElement } - return bytes - } - let numBytes = values.reduce(0) { $0 + $1.count } - var bytes = Data(capacity: numBytes) - values.forEach { bytes.append($0) } - return .byteString(bytes) + return .byteString(try readIndefiniteByteString()) // utf-8 strings case 0x60 ... 0x7B: - let numBytes = try readLength(initByte, base: 0x60) - guard let string = String(data: try stream.readBytes(count: numBytes), encoding: .utf8) else { - throw CBORError.invalidUTF8String - } - return .utf8String(string) + return .utf8String(try readFiniteString(initByte: initByte)) case 0x7F: - return .utf8String(try decodeItemsUntilBreak().map { item -> String in - guard case .utf8String(let string) = item else { throw CBORError.invalidIndefiniteElement } - return string - }.joined(separator: "")) + return .utf8String(try readIndefiniteString()) // arrays case 0x80 ... 0x9B: @@ -200,12 +186,23 @@ public struct CBORReader { let item = try decodeRequiredItem() return .tagged(CBOR.Tag(rawValue: tag), item) - case 0xE0 ... 0xF3: return .simple(initByte - 0xE0) - case 0xF4: return .boolean(false) - case 0xF5: return .boolean(true) - case 0xF6: return .null - case 0xF7: return .undefined - case 0xF8: return .simple(try stream.readByte()) + case 0xE0 ... 0xF3: + return .simple(initByte - 0xE0) + + case 0xF4: + return .boolean(false) + + case 0xF5: + return .boolean(true) + + case 0xF6: + return .null + + case 0xF7: + return .undefined + + case 0xF8: + return .simple(try stream.readByte()) case 0xF9: return .half(try readHalf()) @@ -214,9 +211,35 @@ public struct CBORReader { case 0xFB: return .double(try readDouble()) - case 0xFF: return nil - default: throw CBORError.invalidItemType + case 0xFF: + return nil + + default: + throw CBORError.invalidItemType + } + } + + private func readFiniteString(initByte: UInt8) throws -> String { + let numBytes = try readLength(initByte, base: 0x60) + guard let string = String(data: try stream.readBytes(count: numBytes), encoding: .utf8) else { + throw CBORError.invalidUTF8String } + return string + } + + private func readIndefiniteString() throws -> String { + return try decodeItemsUntilBreak().map { item -> String in + guard case .utf8String(let string) = item else { throw CBORError.invalidIndefiniteElement } + return string + }.joined(separator: "") + } + + private func readIndefiniteByteString() throws -> Data { + let datas = try decodeItemsUntilBreak().map { cbor -> Data in + guard case .byteString(let bytes) = cbor else { throw CBORError.invalidIndefiniteElement } + return bytes + }.joined() + return Data(datas) } } diff --git a/Sources/PotentCBOR/CBORWriter.swift b/Sources/PotentCBOR/CBORWriter.swift index a467cf292..b9bf5e1b2 100644 --- a/Sources/PotentCBOR/CBORWriter.swift +++ b/Sources/PotentCBOR/CBORWriter.swift @@ -113,7 +113,8 @@ public struct CBORWriter { /// Encodes any unsigned integer, `or`ing `modifier` with first byte private func encodeUInt(_ val: T, modifier: UInt8) throws where T: FixedWidthInteger, T: UnsignedInteger { - try encodeVarUInt(UInt64(exactly: val)!, modifier: modifier) + guard let value = UInt64(exactly: val) else { fatalError("invalid unsigned integer") } + try encodeVarUInt(value, modifier: modifier) } /// Encodes any unsigned integer, `or`ing `modifier` with first byte diff --git a/Sources/PotentCodables/AnyCodingKey.swift b/Sources/PotentCodables/AnyCodingKey.swift index 0c566ffcc..fa2018ea4 100644 --- a/Sources/PotentCodables/AnyCodingKey.swift +++ b/Sources/PotentCodables/AnyCodingKey.swift @@ -9,6 +9,11 @@ // public struct AnyCodingKey: CodingKey, Equatable, Hashable { + + enum Error: Swift.Error { + case unsupportedKeyValue(Any) + } + public var stringValue: String public var intValue: Int? @@ -39,12 +44,18 @@ public struct AnyCodingKey: CodingKey, Equatable, Hashable { } // swiftlint:disable:next force_unwrapping - public func key() -> K { + public func key() throws -> K { if let intValue = self.intValue { - return K(intValue: intValue)! + guard let key = K(intValue: intValue) else { + throw Error.unsupportedKeyValue(intValue) + } + return key } else { - return K(stringValue: stringValue)! + guard let key = K(stringValue: stringValue) else { + throw Error.unsupportedKeyValue(stringValue) + } + return key } } diff --git a/Sources/PotentCodables/AnyValue/AnyValue.swift b/Sources/PotentCodables/AnyValue/AnyValue.swift index 65eb3e82c..ad5c41cd3 100644 --- a/Sources/PotentCodables/AnyValue/AnyValue.swift +++ b/Sources/PotentCodables/AnyValue/AnyValue.swift @@ -399,8 +399,11 @@ extension AnyValue { case .uuid(let value): return value case .date(let value): return value case .array(let value): return Array(value.map(\.unwrapped)) - case .dictionary(let value): - return Dictionary(uniqueKeysWithValues: value.compactMap { + case .dictionary(let value): return unwrap(dictionary: value) + } + + func unwrap(dictionary: AnyDictionary) -> Any { + return Dictionary(uniqueKeysWithValues: dictionary.compactMap { guard let value = $1.unwrapped else { return nil } diff --git a/Sources/PotentCodables/AnyValue/AnyValueDecoder.swift b/Sources/PotentCodables/AnyValue/AnyValueDecoder.swift index b6b6cb1dd..b0f4613f0 100644 --- a/Sources/PotentCodables/AnyValue/AnyValueDecoder.swift +++ b/Sources/PotentCodables/AnyValue/AnyValueDecoder.swift @@ -45,16 +45,20 @@ public struct AnyValueDecoderTransform: InternalDecoderTransform { return .nil } + static let interceptedTypes: [Any.Type] = [ + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Float16.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Decodable.Type) -> Bool { - return type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Float16.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type is DictionaryAdapter.Type + return interceptedTypes.contains { $0 == type } || type is DictionaryAdapter.Type } public static func unbox(_ value: AnyValue, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { @@ -95,13 +99,15 @@ public struct AnyValueDecoderTransform: InternalDecoderTransform { guard let key = try decoder.unbox(value: key, as: type.keyType) else { throw DecodingError.typeMismatch( type.keyType, - .init(codingPath: decoder.codingPath, debugDescription: "Expected to decode key of type'\(type.keyType)' but got nil instead") + .init(codingPath: decoder.codingPath, + debugDescription: "Expected to decode key of type'\(type.keyType)' but got nil instead") ) } guard let value = try decoder.unbox(value: value, as: type.valueType) else { throw DecodingError.typeMismatch( type.valueType, - .init(codingPath: decoder.codingPath, debugDescription: "Expected to decode value of type'\(type.keyType)' but got nil instead") + .init(codingPath: decoder.codingPath, + debugDescription: "Expected to decode value of type'\(type.keyType)' but got nil instead") ) } result = type.assign(value: value, forKey: key, to: result) @@ -117,9 +123,9 @@ public struct AnyValueDecoderTransform: InternalDecoderTransform { public static func unbox(_ value: AnyValue, as type: Int.Type, decoder: IVD) throws -> Int? { switch MemoryLayout.size { case 4: - return try unbox(value, as: Int32.self, decoder: decoder).flatMap { Int(exactly: $0)! } + return try unbox(value, as: Int32.self, decoder: decoder).flatMap { Int(exactly: $0) } case 8: - return try unbox(value, as: Int64.self, decoder: decoder).flatMap { Int(exactly: $0)! } + return try unbox(value, as: Int64.self, decoder: decoder).flatMap { Int(exactly: $0) } default: fatalError("unknown memory layout") } @@ -128,9 +134,9 @@ public struct AnyValueDecoderTransform: InternalDecoderTransform { public static func unbox(_ value: AnyValue, as type: UInt.Type, decoder: IVD) throws -> UInt? { switch MemoryLayout.size { case 4: - return try unbox(value, as: UInt32.self, decoder: decoder).flatMap { UInt(exactly: $0)! } + return try unbox(value, as: UInt32.self, decoder: decoder).flatMap { UInt(exactly: $0) } case 8: - return try unbox(value, as: UInt64.self, decoder: decoder).flatMap { UInt(exactly: $0)! } + return try unbox(value, as: UInt64.self, decoder: decoder).flatMap { UInt(exactly: $0) } default: fatalError("unknown memory layout") } @@ -289,9 +295,9 @@ extension Dictionary: DictionaryAdapter where Key: Decodable, Value: Decodable { static var valueType: Decodable.Type { Value.self } static func empty() -> Any { return Dictionary() } static func assign(value: Any, forKey key: Any, to dict: Any) -> Any { - var dict = dict as! Self // swiftlint:disable:this force_cast - let key = key as! Key // swiftlint:disable:this force_cast - let value = value as! Value // swiftlint:disable:this force_cast + var dict = (dict as? Self).unsafelyUnwrapped + let key = (key as? Key).unsafelyUnwrapped + let value = (value as? Value).unsafelyUnwrapped dict[key] = value return dict } @@ -302,9 +308,9 @@ extension OrderedDictionary: DictionaryAdapter where Key: Decodable, Value: Deco static var valueType: Decodable.Type { Value.self } static func empty() -> Any { return OrderedDictionary() } static func assign(value: Any, forKey key: Any, to dict: Any) -> Any { - var dict = dict as! Self // swiftlint:disable:this force_cast - let key = key as! Key // swiftlint:disable:this force_cast - let value = value as! Value // swiftlint:disable:this force_cast + var dict = (dict as? Self).unsafelyUnwrapped + let key = (key as? Key).unsafelyUnwrapped + let value = (value as? Value).unsafelyUnwrapped dict[key] = value return dict } diff --git a/Sources/PotentCodables/AnyValue/AnyValueEncoder.swift b/Sources/PotentCodables/AnyValue/AnyValueEncoder.swift index 3c12b761d..63763ef24 100644 --- a/Sources/PotentCodables/AnyValue/AnyValueEncoder.swift +++ b/Sources/PotentCodables/AnyValue/AnyValueEncoder.swift @@ -49,16 +49,20 @@ public struct AnyValueEncoderTransform: InternalEncoderTransform { return .array([]) } + static let interceptedTypes: [Any.Type] = [ + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Float16.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Encodable.Type) -> Bool { - return type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Float16.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type is DictionaryAdapter.Type + return interceptedTypes.contains { $0 == type } || type is DictionaryAdapter.Type } public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> AnyValue { @@ -98,13 +102,15 @@ public struct AnyValueEncoderTransform: InternalEncoderTransform { guard let boxedKey = try encoder.box(value: key) else { throw EncodingError.invalidValue( key, - .init(codingPath: encoder.codingPath, debugDescription: "Expected to encode key '\(key)' but got nil instead") + .init(codingPath: encoder.codingPath, + debugDescription: "Expected to encode key '\(key)' but got nil instead") ) } guard let boxedValue = try encoder.box(value: value) else { throw EncodingError.invalidValue( key, - .init(codingPath: encoder.codingPath, debugDescription: "Expected to encode value '\(value)' but got nil instead") + .init(codingPath: encoder.codingPath, + debugDescription: "Expected to encode value '\(value)' but got nil instead") ) } dict[boxedKey] = boxedValue diff --git a/Sources/PotentCodables/ValueDecoder.swift b/Sources/PotentCodables/ValueDecoder.swift index f447905f3..3950e1220 100644 --- a/Sources/PotentCodables/ValueDecoder.swift +++ b/Sources/PotentCodables/ValueDecoder.swift @@ -97,7 +97,7 @@ open class ValueDecoder where Transform: InternalDecoderTransf /// The options set on the top-level decoder. open var options: Transform.Options { fatalError() } - open var state: Transform.State! + open var state: Transform.State? // MARK: - Decoding Values @@ -243,10 +243,12 @@ extension ValueDecoder where Transform: InternalValueParser { // MARK: - InternalValueDecoder -/// `InternalValueDocoder` is the `Decoder` implementation that is passed to `Decodable` objects to allow them to perform decoding +/// `InternalValueDocoder` is the `Decoder` implementation that is passed +/// to `Decodable` objects to allow them to perform decoding /// -/// Although the type represents an implementation of the public API for `Decodable` it can also be used by implementations of -/// `InternalDecoderTransform` as the instance is also passed to all members of the transform. +/// Although the type represents an implementation of the public API for +/// `Decodable` it can also be used by implementations of `InternalDecoderTransform` +/// as the instance is also passed to all members of the transform. public class InternalValueDecoder: Decoder where Transform: InternalDecoderTransform, Value == Transform.Value { @@ -350,8 +352,10 @@ private struct ValueDecodingStorage where Value: PotentCodables.Value { } fileprivate var topContainer: Value { - precondition(containers.count > 0, "Empty container stack.") - return containers.last! + guard let container = containers.last else { + fatalError("Empty container stack.") + } + return container } fileprivate mutating func push(container: Value) { @@ -385,12 +389,17 @@ private struct ValueKeyedDecodingContainer: Keye // MARK: - Initialization /// Initializes `self` by referencing the given decoder and container. - fileprivate init(referencing decoder: InternalValueDecoder, wrapping container: OrderedDictionary) throws { + fileprivate init( + referencing decoder: InternalValueDecoder, + wrapping container: OrderedDictionary + ) throws { self.decoder = decoder switch decoder.options.keyDecodingStrategy { case .convertFromSnakeCase: // Convert the snake case keys in the container to camel case. - // If we hit a duplicate key after conversion, then we'll use the first one we saw. Effectively an undefined behavior with dictionaries. + // If we hit a duplicate key after conversion, then we'll use + // the first one we saw. Effectively an undefined behavior with + // dictionaries. self.container = try OrderedDictionary( container.map { (KeyDecodingStrategy.convertFromSnakeCase($0.key), $0.value) @@ -497,11 +506,12 @@ private struct ValueKeyedDecodingContainer: Keye defer { self.decoder.codingPath.removeLast() } guard let value = self.container[key.stringValue] else { + let container = KeyedDecodingContainer.self throw DecodingError.keyNotFound( key, DecodingError.Context( codingPath: codingPath, - debugDescription: "Cannot get \(KeyedDecodingContainer.self) -- no value found for key \(errorDescription(of: key))" + debugDescription: "Cannot get \(container) -- no value found for key \(errorDescription(of: key))" ) ) } @@ -592,7 +602,7 @@ private struct ValueUnkeyedDecodingContainer: UnkeyedDecodingC } public var isAtEnd: Bool { - return currentIndex >= count! + return currentIndex >= container.count } public mutating func decodeNil() throws -> Bool { diff --git a/Sources/PotentCodables/ValueEncoder.swift b/Sources/PotentCodables/ValueEncoder.swift index 9647051a2..6c7f72686 100644 --- a/Sources/PotentCodables/ValueEncoder.swift +++ b/Sources/PotentCodables/ValueEncoder.swift @@ -320,8 +320,7 @@ public class InternalValueEncoder: Encoder where Transform: In return try Transform.unkeyedValuesToValue(rolled, encoder: self) } else { - // swiftlint:disable:next force_cast - return value as! Value + return (value as? Value).unsafelyUnwrapped } } @@ -417,8 +416,10 @@ private struct ValueEncodingStorage where Transform: InternalE } fileprivate mutating func popContainer() -> Any { - precondition(containers.count > 0, "Empty container stack.") - return containers.popLast()! + guard let container = containers.popLast() else { + fatalError("Empty container stack.") + } + return container } } @@ -866,7 +867,7 @@ public extension InternalValueEncoder { return try Transform.box(value, interceptedType: type, encoder: self) } else if value is ValueStringDictionaryEncodableMarker { - return try box((value as Any) as! [String: Encodable]) // swiftlint:disable:this force_cast + return try box(((value as Any) as? [String: Encodable]).unsafelyUnwrapped) } return try Transform.box(value, otherType: type, encoder: self) } @@ -1062,8 +1063,9 @@ public extension InternalEncoderTransform { } static func box(_ value: Any, otherType: Encodable.Type, encoder: IVE) throws -> Value? { - // swiftlint:disable:next force_cast - return try encoder.subEncode { subEncoder in try (value as! Encodable).encode(to: subEncoder.encoder) } + return try encoder.subEncode { subEncoder in + try ((value as? Encodable).unsafelyUnwrapped).encode(to: subEncoder.encoder) + } } } diff --git a/Sources/PotentJSON/JSONDecoder.swift b/Sources/PotentJSON/JSONDecoder.swift index b6ae8af03..0e2122ade 100644 --- a/Sources/PotentJSON/JSONDecoder.swift +++ b/Sources/PotentJSON/JSONDecoder.swift @@ -107,16 +107,20 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser public let userInfo: [CodingUserInfoKey: Any] } + static let interceptedTypes: [Any.Type] = [ + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Float16.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Decodable.Type) -> Bool { - return type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Float16.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type == AnyValue.self + return interceptedTypes.contains { $0 == type } } public static func unbox(_ value: JSON, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { @@ -397,15 +401,25 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser return try decoder.subDecode(with: value) { try Date(from: $0) } case .secondsSince1970: - let double = try unbox(value, as: Double.self, decoder: decoder)! - return Date(timeIntervalSince1970: double) + return try unbox(value, as: Double.self, decoder: decoder).map { Date(timeIntervalSince1970: $0) } case .millisecondsSince1970: - let double = try unbox(value, as: Double.self, decoder: decoder)! - return Date(timeIntervalSince1970: double / 1000.0) + return try unbox(value, as: Double.self, decoder: decoder).map { Date(timeIntervalSince1970: $0 / 1000.0) } case .iso8601: - let string = try unbox(value, as: String.self, decoder: decoder)! + return try decodeISO8601(from: value) + + case .formatted(let formatter): + return try decodeFormatted(from: value, formatter: formatter) + + case .custom(let closure): + return try decoder.subDecode(with: value) { try closure($0) } + } + + func decodeISO8601(from value: JSON) throws -> Date? { + guard let string = try unbox(value, as: String.self, decoder: decoder) else { + return nil + } guard let zonedDate = ZonedDate(iso8601Encoded: string) else { throw DecodingError.dataCorrupted(.init( codingPath: decoder.codingPath, @@ -413,9 +427,12 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser )) } return zonedDate.utcDate + } - case .formatted(let formatter): - let string = try unbox(value, as: String.self, decoder: decoder)! + func decodeFormatted(from value: JSON, formatter: DateFormatter) throws -> Date? { + guard let string = try unbox(value, as: String.self, decoder: decoder) else { + return nil + } guard let date = formatter.date(from: string) else { throw DecodingError.dataCorrupted(.init( codingPath: decoder.codingPath, @@ -423,9 +440,6 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser )) } return date - - case .custom(let closure): - return try decoder.subDecode(with: value) { try closure($0) } } } @@ -437,6 +451,13 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser return try decoder.subDecode(with: value) { try Data(from: $0) } case .base64: + return try decodeBase64(from: value) + + case .custom(let closure): + return try decoder.subDecode(with: value) { try closure($0) } + } + + func decodeBase64(from value: JSON) throws -> Data { guard case .string(let string) = value else { throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } @@ -449,18 +470,28 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser } return data - - case .custom(let closure): - return try decoder.subDecode(with: value) { try closure($0) } } } public static func unbox(_ value: JSON, as type: AnyValue.Type, decoder: IVD) throws -> AnyValue { switch value { - case .null: return .nil - case .bool(let value): return .bool(value) - case .string(let value): return .string(value) + case .null: + return .nil + case .bool(let value): + return .bool(value) + case .string(let value): + return .string(value) case .number(let value): + return decode(from: value) + case .array(let value): + return .array(try value.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) + case .object(let value): + return .dictionary(AnyValue.AnyDictionary(uniqueKeysWithValues: try value.map { key, value in + (.string(key), try unbox(value, as: AnyValue.self, decoder: decoder)) + })) + } + + func decode(from value: JSON.Number) -> AnyValue { switch value.numberValue { case .none: return .nil case let int as Int: @@ -478,12 +509,6 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser default: fatalError("numberValue returned unsupported value") } - case .array(let value): - return .array(try value.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) - case .object(let value): - return .dictionary(AnyValue.AnyDictionary(uniqueKeysWithValues: try value.map { key, value in - (.string(key), try unbox(value, as: AnyValue.self, decoder: decoder)) - })) } } @@ -521,6 +546,7 @@ public struct JSONDecoderTransform: InternalDecoderTransform, InternalValueDeser extension JSONDecoderTransform.Options { + // swiftlint:disable:next identifier_name var nonConformingFloatDecodingStrategyStrings: (posInf: String, negInf: String, nan: String)? { if case .convertFromString(positiveInfinity: let posInfStr, negativeInfinity: let negInfStr, nan: let nanStr) = nonConformingFloatDecodingStrategy { diff --git a/Sources/PotentJSON/JSONEncoder.swift b/Sources/PotentJSON/JSONEncoder.swift index 5bb407546..89f282655 100644 --- a/Sources/PotentJSON/JSONEncoder.swift +++ b/Sources/PotentJSON/JSONEncoder.swift @@ -62,7 +62,8 @@ public class JSONEncoder: ValueEncoder, EncodesToStr /// Encode the `Date` as a custom value encoded by the given closure. /// - /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. + /// If the closure fails to encode a value into the given encoder, the encoder + /// will encode an empty automatic container in its place. case custom((Date, Encoder) throws -> Void) } @@ -77,7 +78,8 @@ public class JSONEncoder: ValueEncoder, EncodesToStr /// Encode the `Data` as a custom value encoded by the given closure. /// - /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. + /// If the closure fails to encode a value into the given encoder, the encoder + /// will encode an empty automatic container in its place. case custom((Data, Encoder) throws -> Void) } @@ -141,16 +143,20 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria public let userInfo: [CodingUserInfoKey: Any] } + static let interceptedTypes: [Any.Type] = [ + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Float16.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Encodable.Type) -> Bool { - return type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Float16.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type == AnyValue.self + return interceptedTypes.contains { $0 == type } } public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> JSON { @@ -386,7 +392,11 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria case .array(let value): return .array(try value.map { try box($0, encoder: encoder) }) case .dictionary(let value): - return .object(JSON.Object(uniqueKeysWithValues: try value.map { key, value in + return .object(try encodeObject(from: value)) + } + + func encodeObject(from value: AnyValue.AnyDictionary) throws -> JSON.Object { + return JSON.Object(uniqueKeysWithValues: try value.map { key, value in let boxedValue = try box(value, encoder: encoder) if let stringKey = key.stringValue { return (stringKey, boxedValue) @@ -396,7 +406,7 @@ public struct JSONEncoderTransform: InternalEncoderTransform, InternalValueSeria } throw EncodingError.invalidValue(value, .init(codingPath: encoder.codingPath, debugDescription: "Dictionary contains non-string values")) - })) + }) } } diff --git a/Sources/PotentJSON/JSONReader.swift b/Sources/PotentJSON/JSONReader.swift index e313c5d94..e71df650d 100644 --- a/Sources/PotentJSON/JSONReader.swift +++ b/Sources/PotentJSON/JSONReader.swift @@ -21,7 +21,7 @@ import Foundation -struct JSONReader { +internal struct JSONReader { public enum Error: Swift.Error { @@ -78,10 +78,10 @@ struct JSONReader { func takeString(_ begin: Index, end: Index) throws -> String { let byteLength = begin.distance(to: end) - guard let chunk = String( - data: Data(bytes: buffer.baseAddress!.advanced(by: begin), count: byteLength), - encoding: .utf8 - ) else { + guard + let baseAddress = buffer.baseAddress, + let chunk = String(data: Data(bytes: baseAddress.advanced(by: begin), count: byteLength), encoding: .utf8) + else { throw Error.invalidData(.invalidString, position: distanceFromStart(begin)) } return chunk @@ -212,7 +212,10 @@ struct JSONReader { guard isLeadSurrogate || isTrailSurrogate else { // The code units that are neither lead surrogates nor trail surrogates // form valid unicode scalars. - return (String(UnicodeScalar(codeUnit)!), index) + guard let scalar = UnicodeScalar(codeUnit) else { + throw Error.invalidData(.invalidEscapeSequence, position: source.distanceFromStart(input)) + } + return (String(scalar), index) } // Surrogates must always come in pairs. diff --git a/Sources/PotentJSON/JSONSerialization.swift b/Sources/PotentJSON/JSONSerialization.swift index b6eddb348..b8766af1b 100644 --- a/Sources/PotentJSON/JSONSerialization.swift +++ b/Sources/PotentJSON/JSONSerialization.swift @@ -75,7 +75,14 @@ public enum JSONSerialization { } public static func data(from json: JSON, options: WritingOptions = []) throws -> Data { - return try string(from: json, options: options).data(using: .utf8)! + guard let data = try string(from: json, options: options).data(using: .utf8) else { + throw DecodingError + .dataCorrupted( + DecodingError + .Context(codingPath: [], debugDescription: "String cannot be decoded as UTF-8", underlyingError: nil) + ) + } + return data } public static func string(from json: JSON, options: WritingOptions = []) throws -> String { diff --git a/Sources/PotentJSON/JSONWriter.swift b/Sources/PotentJSON/JSONWriter.swift index 7fa68ba59..eca1d38a6 100644 --- a/Sources/PotentJSON/JSONWriter.swift +++ b/Sources/PotentJSON/JSONWriter.swift @@ -20,7 +20,7 @@ import Foundation -struct JSONWriter { +internal struct JSONWriter { enum Error: Swift.Error { case invalidNumber(Double) @@ -32,7 +32,12 @@ struct JSONWriter { private let sortedKeys: Bool private let writer: (String) -> Void - init(escapeSlashes: Bool = false, pretty: Bool = false, sortedKeys: Bool = false, writer: @escaping (String) -> Void) { + init( + escapeSlashes: Bool = false, + pretty: Bool = false, + sortedKeys: Bool = false, + writer: @escaping (String) -> Void + ) { self.esccapeSlashes = escapeSlashes self.pretty = pretty self.sortedKeys = sortedKeys diff --git a/Sources/PotentYAML/YAMLDecoder.swift b/Sources/PotentYAML/YAMLDecoder.swift index 5c9deb8d5..6b33fc514 100644 --- a/Sources/PotentYAML/YAMLDecoder.swift +++ b/Sources/PotentYAML/YAMLDecoder.swift @@ -102,16 +102,20 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser public let userInfo: [CodingUserInfoKey: Any] } + static let interceptedTypes: [Any.Type] = [ + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Float16.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Decodable.Type) -> Bool { - return type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Float16.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type == AnyValue.self + return interceptedTypes.contains { $0 == type } } public static func unbox(_ value: YAML, interceptedType: Decodable.Type, decoder: IVD) throws -> Any? { @@ -176,6 +180,19 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser return result } + static func decode(_ from: YAML.Number, at codingPath: [CodingKey]) throws -> T? + where T: BinaryFloatingPoint & LosslessStringConvertible { + if from.isNaN { + return T.nan + } + else if from.isInfinity { + return from.isNegative ? -T.infinity : +T.infinity + } + else { + return try coerce(from, at: codingPath) + } + } + public static func unbox(_ value: YAML, as type: Int.Type, decoder: IVD) throws -> Int? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) @@ -291,16 +308,7 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser public static func unbox(_ value: YAML, as type: Float16.Type, decoder: IVD) throws -> Float16? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): - if number.isNaN { - return Float16.nan - } - else if number.isInfinity { - return number.isNegative ? -Float16.infinity : +Float16.infinity - } - else { - return try coerce(number, at: decoder.codingPath) - } + case .float(let number, _): return try decode(number, at: decoder.codingPath) case .null: return nil default: break @@ -311,16 +319,7 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser public static func unbox(_ value: YAML, as type: Float.Type, decoder: IVD) throws -> Float? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): - if number.isNaN { - return Float.nan - } - else if number.isInfinity { - return number.isNegative ? -Float.infinity : +Float.infinity - } - else { - return try coerce(number, at: decoder.codingPath) - } + case .float(let number, _): return try decode(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) @@ -330,16 +329,7 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser public static func unbox(_ value: YAML, as type: Double.Type, decoder: IVD) throws -> Double? { switch value { case .integer(let number, _): return try coerce(number, at: decoder.codingPath) - case .float(let number, _): - if number.isNaN { - return Double.nan - } - else if number.isInfinity { - return number.isNegative ? -Double.infinity : +Double.infinity - } - else { - return try coerce(number, at: decoder.codingPath) - } + case .float(let number, _): return try decode(number, at: decoder.codingPath) case .null: return nil case let yaml: throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) @@ -348,7 +338,14 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser public static func unbox(_ value: YAML, as type: Decimal.Type, decoder: IVD) throws -> Decimal? { switch value { - case .integer(let number, _): + case .integer(let number, _): return try decodeInteger(number) + case .float(let number, _): return try decodeFloat(number) + case .null: return nil + case let yaml: + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) + } + + func decodeInteger(_ number: YAML.Number) throws -> Decimal { guard let decimal = Decimal(string: number.value) else { throw DecodingError.typeMismatch( Decimal.self, @@ -356,7 +353,9 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser debugDescription: "Decimal unable to parse number")) } return decimal - case .float(let number, _): + } + + func decodeFloat(_ number: YAML.Number) throws -> Decimal { if number.isNaN { return Decimal.nan } @@ -373,9 +372,6 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser debugDescription: "Decimal unable to parse number")) } return decimal - case .null: return nil - case let yaml: - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: yaml) } } @@ -423,15 +419,25 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser return try decoder.subDecode(with: value) { try Date(from: $0) } case .secondsSince1970: - let double = try unbox(value, as: Double.self, decoder: decoder)! - return Date(timeIntervalSince1970: double) + return try unbox(value, as: Double.self, decoder: decoder).map { Date(timeIntervalSince1970: $0) } case .millisecondsSince1970: - let double = try unbox(value, as: Double.self, decoder: decoder)! - return Date(timeIntervalSince1970: double / 1000.0) + return try unbox(value, as: Double.self, decoder: decoder).map { Date(timeIntervalSince1970: $0 / 1000.0) } case .iso8601: - let string = try unbox(value, as: String.self, decoder: decoder)! + return try decodeISO8601(from: value) + + case .formatted(let formatter): + return try decodeFormatted(from: value, formatter: formatter) + + case .custom(let closure): + return try decoder.subDecode(with: value) { try closure($0) } + } + + func decodeISO8601(from value: YAML) throws -> Date? { + guard let string = try unbox(value, as: String.self, decoder: decoder) else { + return nil + } guard let date = ZonedDate(iso8601Encoded: string)?.utcDate else { throw DecodingError.dataCorrupted(.init( codingPath: decoder.codingPath, @@ -439,9 +445,12 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser )) } return date + } - case .formatted(let formatter): - let string = try unbox(value, as: String.self, decoder: decoder)! + func decodeFormatted(from value: YAML, formatter: DateFormatter) throws -> Date? { + guard let string = try unbox(value, as: String.self, decoder: decoder) else { + return nil + } guard let date = formatter.date(from: string) else { throw DecodingError.dataCorrupted(.init( codingPath: decoder.codingPath, @@ -449,9 +458,6 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser )) } return date - - case .custom(let closure): - return try decoder.subDecode(with: value) { try closure($0) } } } @@ -463,6 +469,13 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser return try decoder.subDecode(with: value) { try Data(from: $0) } case .base64: + return try decodeBase64(from: value) + + case .custom(let closure): + return try decoder.subDecode(with: value) { try closure($0) } + } + + func decodeBase64(from value: YAML) throws -> Data { guard case .string(let string, _, _, _) = value else { throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } @@ -475,18 +488,32 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser } return data - - case .custom(let closure): - return try decoder.subDecode(with: value) { try closure($0) } } } public static func unbox(_ value: YAML, as type: AnyValue.Type, decoder: IVD) throws -> AnyValue { switch value { - case .null: return .nil - case .bool(let value, _): return .bool(value) - case .string(let value, _, _, _): return .string(value) + case .null: + return .nil + case .bool(let value, _): + return .bool(value) + case .string(let value, _, _, _): + return .string(value) case .integer(let value, anchor: _), .float(let value, anchor: _): + return decode(from: value) + case .sequence(let value, _, _, _): + return .array(try value.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) + case .mapping(let value, _, _, _): + return .dictionary(AnyValue.AnyDictionary(uniqueKeysWithValues: try value.map { entry in + (try unbox(entry.key, as: AnyValue.self, decoder: decoder), + try unbox(entry.value, as: AnyValue.self, decoder: decoder)) + })) + default: + break + } + throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) + + func decode(from value: YAML.Number) -> AnyValue { switch value.numberValue { case .none: return .nil case let int as Int: @@ -504,17 +531,7 @@ public struct YAMLDecoderTransform: InternalDecoderTransform, InternalValueDeser default: fatalError("numberValue returned unsupported value") } - case .sequence(let value, _, _, _): - return .array(try value.map { try unbox($0, as: AnyValue.self, decoder: decoder) }) - case .mapping(let value, _, _, _): - return .dictionary(AnyValue.AnyDictionary(uniqueKeysWithValues: try value.map { entry in - (try unbox(entry.key, as: AnyValue.self, decoder: decoder), - try unbox(entry.value, as: AnyValue.self, decoder: decoder)) - })) - default: - break } - throw DecodingError.typeMismatch(at: decoder.codingPath, expectation: type, reality: value) } public static func valueToUnkeyedValues(_ value: YAML, decoder: IVD) throws -> UnkeyedValues? { diff --git a/Sources/PotentYAML/YAMLEncoder.swift b/Sources/PotentYAML/YAMLEncoder.swift index 71375973a..568ebd620 100644 --- a/Sources/PotentYAML/YAMLEncoder.swift +++ b/Sources/PotentYAML/YAMLEncoder.swift @@ -56,7 +56,8 @@ public class YAMLEncoder: ValueEncoder, EncodesToStr /// Encode the `Date` as a custom value encoded by the given closure. /// - /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. + /// If the closure fails to encode a value into the given encoder, the encoder + /// will encode an empty automatic container in its place. case custom((Date, Encoder) throws -> Void) } @@ -71,7 +72,8 @@ public class YAMLEncoder: ValueEncoder, EncodesToStr /// Encode the `Data` as a custom value encoded by the given closure. /// - /// If the closure fails to encode a value into the given encoder, the encoder will encode an empty automatic container in its place. + /// If the closure fails to encode a value into the given encoder, the encoder + /// will encode an empty automatic container in its place. case custom((Data, Encoder) throws -> Void) } @@ -120,16 +122,20 @@ public struct YAMLEncoderTransform: InternalEncoderTransform, InternalValueSeria public let userInfo: [CodingUserInfoKey: Any] } + static let interceptedTypes: [Any.Type] = [ + Date.self, NSDate.self, + Data.self, NSData.self, + URL.self, NSURL.self, + UUID.self, NSUUID.self, + Float16.self, + Decimal.self, NSDecimalNumber.self, + BigInt.self, + BigUInt.self, + AnyValue.self, + ] + public static func intercepts(_ type: Encodable.Type) -> Bool { - return type == Date.self || type == NSDate.self - || type == Data.self || type == NSData.self - || type == URL.self || type == NSURL.self - || type == UUID.self || type == NSUUID.self - || type == Float16.self - || type == Decimal.self || type == NSDecimalNumber.self - || type == BigInt.self - || type == BigUInt.self - || type == AnyValue.self + return interceptedTypes.contains { $0 == type } } public static func box(_ value: Any, interceptedType: Encodable.Type, encoder: IVE) throws -> YAML { diff --git a/Sources/PotentYAML/YAMLReader.swift b/Sources/PotentYAML/YAMLReader.swift index 4410a4a0d..81a4bf0c1 100644 --- a/Sources/PotentYAML/YAMLReader.swift +++ b/Sources/PotentYAML/YAMLReader.swift @@ -144,20 +144,20 @@ public enum YAMLReader { return result } - static let nullRegex = RegEx(pattern: #"^(null|Null|NULL|~)$"#)! - static let trueRegex = RegEx(pattern: #"^(true|True|TRUE)$"#)! - static let falseRegex = RegEx(pattern: #"^(false|False|FALSE)$"#)! - static let integerRegex = RegEx(pattern: #"^(([-+]?[0-9]+)|(0o[0-7]+)|(0x[0-9a-fA-F]+))$"#)! - static let floatRegex = RegEx(pattern: #"^([-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?)$"#)! - static let infinityRegex = RegEx(pattern: #"^([-+]?(\.inf|\.Inf|\.INF))$"#)! - static let nanRegex = RegEx(pattern: #"^(\.nan|\.NaN|\.NAN)$"#)! + private static let nullRegex = RegEx(pattern: #"^(null|Null|NULL|~)$"#) + private static let trueRegex = RegEx(pattern: #"^(true|True|TRUE)$"#) + private static let falseRegex = RegEx(pattern: #"^(false|False|FALSE)$"#) + private static let integerRegex = RegEx(pattern: #"^(([-+]?[0-9]+)|(0o[0-7]+)|(0x[0-9a-fA-F]+))$"#) + private static let floatRegex = RegEx(pattern: #"^([-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?)$"#) + private static let infinityRegex = RegEx(pattern: #"^([-+]?(\.inf|\.Inf|\.INF))$"#) + private static let nanRegex = RegEx(pattern: #"^(\.nan|\.NaN|\.NAN)$"#) static func scalar(value: String, style: fy_scalar_style, tag: YAML.Tag?, anchor: String?) throws -> YAML { - let stringStyle = YAML.StringStyle(rawValue: style.rawValue)! + let stringStyle = YAML.StringStyle(rawValue: style.rawValue) ?? .any switch tag { case .none: - return try untaggedScalar(value: value, stringStyle: stringStyle, style: style, tag: tag, anchor: anchor) + return try untaggedScalar(value: value, stringStyle: stringStyle, style: style, anchor: anchor) case .some(YAML.Tag.null): return .null(anchor: anchor) @@ -181,7 +181,6 @@ public enum YAMLReader { value: String, stringStyle: YAML.StringStyle, style: fy_scalar_style, - tag: YAML.Tag?, anchor: String? ) throws -> YAML { @@ -351,7 +350,7 @@ extension String { } -class RegEx { +private class RegEx { public struct Options: OptionSet { public let rawValue: Int32 @@ -380,12 +379,12 @@ class RegEx { private var regex = regex_t() - init?(pattern: String, options: Options = [.extended]) { + init(pattern: String, options: Options = [.extended]) { let res = pattern.withCString { patternPtr in regcomp(®ex, patternPtr, options.rawValue) } guard res == 0 else { - return nil + fatalError("invalid pattern") } } @@ -395,7 +394,7 @@ class RegEx { func matches(string: String, options: MatchOptions = []) -> Bool { return string.withCString { stringPtr in - return regexec(®ex, stringPtr, 0, nil, options.rawValue) == 0 + regexec(®ex, stringPtr, 0, nil, options.rawValue) == 0 } } diff --git a/Sources/PotentYAML/YAMLSerialization.swift b/Sources/PotentYAML/YAMLSerialization.swift index 49998776a..eaad4aaf9 100644 --- a/Sources/PotentYAML/YAMLSerialization.swift +++ b/Sources/PotentYAML/YAMLSerialization.swift @@ -66,7 +66,14 @@ public enum YAMLSerialization { } public static func data(from yaml: YAML, options: WritingOptions = []) throws -> Data { - return try string(from: yaml, options: options).data(using: .utf8)! + guard let data = try string(from: yaml, options: options).data(using: .utf8) else { + throw DecodingError + .dataCorrupted( + DecodingError + .Context(codingPath: [], debugDescription: "String cannot be decoded as UTF-8", underlyingError: nil) + ) + } + return data } public static func string(from yaml: YAML, options: WritingOptions = []) throws -> String { diff --git a/Sources/PotentYAML/YAMLWriter.swift b/Sources/PotentYAML/YAMLWriter.swift index b69987581..02687eae8 100644 --- a/Sources/PotentYAML/YAMLWriter.swift +++ b/Sources/PotentYAML/YAMLWriter.swift @@ -11,7 +11,7 @@ import Cfyaml import Foundation -struct YAMLWriter { +internal struct YAMLWriter { enum Error: Swift.Error { case createEmitterFailed @@ -29,7 +29,9 @@ struct YAMLWriter { len: Int32, userInfo: UnsafeMutableRawPointer? ) -> Int32 { - let writer = userInfo!.assumingMemoryBound(to: Writer.self).pointee + guard let writer = userInfo?.assumingMemoryBound(to: Writer.self).pointee else { + fatalError() + } guard let str = str else { writer(nil) return 0 @@ -80,13 +82,8 @@ struct YAMLWriter { emit(emitter: emitter, scalar: "null", style: FYSS_PLAIN, anchor: anchor, tag: nil) case .string(let string, style: let style, tag: let tag, anchor: let anchor): - emit( - emitter: emitter, - scalar: string, - style: fy_scalar_style(rawValue: style.rawValue), - anchor: anchor, - tag: tag?.rawValue - ) + let scalarStyle = fy_scalar_style(rawValue: style.rawValue) + emit(emitter: emitter, scalar: string, style: scalarStyle, anchor: anchor, tag: tag?.rawValue) case .integer(let integer, anchor: let anchor): emit(emitter: emitter, scalar: integer.value, style: FYSS_PLAIN, anchor: anchor, tag: nil) @@ -98,36 +95,10 @@ struct YAMLWriter { emit(emitter: emitter, scalar: bool ? "true" : "false", style: FYSS_PLAIN, anchor: anchor, tag: nil) case .sequence(let sequence, style: let style, tag: let tag, anchor: let anchor): - emit( - emitter: emitter, - type: FYET_SEQUENCE_START, - args: style.nodeStyle.rawValue, - anchor.varArg, - (tag?.rawValue).varArg - ) - try sequence.forEach { element in - try emit(emitter: emitter, value: element, sortedKeys: sortedKeys) - } - emit(emitter: emitter, type: FYET_SEQUENCE_END) - - case .mapping(var mapping, style: let style, tag: let tag, anchor: let anchor): - emit( - emitter: emitter, - type: FYET_MAPPING_START, - args: style.nodeStyle.rawValue, - anchor.varArg, - (tag?.rawValue).varArg - ) - if sortedKeys { - mapping = mapping.sorted { - $0.key.description < $1.key.description - } - } - try mapping.forEach { entry in - try emit(emitter: emitter, value: entry.key, sortedKeys: sortedKeys) - try emit(emitter: emitter, value: entry.value, sortedKeys: sortedKeys) - } - emit(emitter: emitter, type: FYET_MAPPING_END) + try emit(emitter: emitter, sequence: sequence, sortedKeys: sortedKeys, style: style, anchor: anchor, tag: tag) + + case .mapping(let mapping, style: let style, tag: let tag, anchor: let anchor): + try emit(emitter: emitter, mapping: mapping, sortedKeys: sortedKeys, style: style, anchor: anchor, tag: tag) case .alias(let alias): emit(emitter: emitter, alias: alias) @@ -135,6 +106,55 @@ struct YAMLWriter { } } + private static func emit( + emitter: OpaquePointer, + mapping: YAML.Mapping, + sortedKeys: Bool, + style: YAML.CollectionStyle, + anchor: String?, + tag: YAML.Tag? + ) throws { + var mapping = mapping + emit( + emitter: emitter, + type: FYET_MAPPING_START, + args: style.nodeStyle.rawValue, + anchor.varArg, + (tag?.rawValue).varArg + ) + if sortedKeys { + mapping = mapping.sorted { + $0.key.description < $1.key.description + } + } + try mapping.forEach { entry in + try emit(emitter: emitter, value: entry.key, sortedKeys: sortedKeys) + try emit(emitter: emitter, value: entry.value, sortedKeys: sortedKeys) + } + emit(emitter: emitter, type: FYET_MAPPING_END) + } + + private static func emit( + emitter: OpaquePointer, + sequence: [YAML], + sortedKeys: Bool, + style: YAML.CollectionStyle, + anchor: String?, + tag: YAML.Tag? + ) throws { + emit( + emitter: emitter, + type: FYET_SEQUENCE_START, + args: style.nodeStyle.rawValue, + anchor.varArg, + (tag?.rawValue).varArg + ) + try sequence.forEach { element in + try emit(emitter: emitter, value: element, sortedKeys: sortedKeys) + } + emit(emitter: emitter, type: FYET_SEQUENCE_END) + } + private static func emit( emitter: OpaquePointer, scalar: String, diff --git a/Tests/.swiftlint.yml b/Tests/.swiftlint.yml index d5ed36eca..c2443397e 100644 --- a/Tests/.swiftlint.yml +++ b/Tests/.swiftlint.yml @@ -6,4 +6,4 @@ disabled_rules: - trailing_comma file_length: - - 3000 +- 3000 diff --git a/Tests/ASN1DERTests.swift b/Tests/ASN1DERTests.swift new file mode 100644 index 000000000..471652fa1 --- /dev/null +++ b/Tests/ASN1DERTests.swift @@ -0,0 +1,67 @@ +// +// ASN1DERTests.swift +// PotentCodables +// +// Copyright © 2021 Outfox, inc. +// +// +// Distributed under the MIT License, See LICENSE for details. +// + +import Foundation +@testable import PotentASN1 +import XCTest + + +class ASN1DERTests: XCTestCase { + + func testWrite1ByteLengthValue() throws { + let writer = ASN1DERWriter() + try writer.append(tag: 4, length: 0x7f) + + XCTAssertEqual(writer.data, Data([0x4, 0x7f])) + } + + func testWrite2ByteLengthValue() throws { + let writer = ASN1DERWriter() + try writer.append(tag: 4, length: 0x80) + + XCTAssertEqual(writer.data, Data([0x4, 0x81, 0x80])) + } + + func testWrite3ByteLengthValueLow() throws { + let writer = ASN1DERWriter() + try writer.append(tag: 4, length: 0x01_00) + + XCTAssertEqual(writer.data, Data([0x4, 0x82, 0x01, 0x00])) + } + + func testWrite3ByteLengthValueHigh() throws { + let writer = ASN1DERWriter() + try writer.append(tag: 4, length: 0xff_ff) + + XCTAssertEqual(writer.data, Data([0x4, 0x82, 0xff, 0xff])) + } + + func testWrite4ByteLengthValueLow() throws { + let writer = ASN1DERWriter() + try writer.append(tag: 4, length: 0x01_00_00) + + XCTAssertEqual(writer.data, Data([0x4, 0x83, 0x01, 0x00, 0x00])) + } + + func testWrite4ByteLengthValueHigh() throws { + let writer = ASN1DERWriter() + try writer.append(tag: 4, length: 0xff_ff_ff) + + XCTAssertEqual(writer.data, Data([0x4, 0x83, 0xff, 0xff, 0xff])) + } + + func testWrite5ByteLengthValueLow() throws { + let writer = ASN1DERWriter() + try writer.append(tag: 4, length: 0x01_00_00_00) + + XCTAssertEqual(writer.data, Data([0x4, 0x84, 0x01, 0x00, 0x00, 0x00])) + } + +} diff --git a/Tests/AnyCodingKeyTests.swift b/Tests/AnyCodingKeyTests.swift index bf58f62bb..207239c36 100644 --- a/Tests/AnyCodingKeyTests.swift +++ b/Tests/AnyCodingKeyTests.swift @@ -42,7 +42,7 @@ class AnyCodingKeyTests: XCTestCase { let strKey = AnyCodingKey(StrKey.test) XCTAssertEqual(strKey.stringValue, "test") XCTAssertNil(strKey.intValue) - XCTAssertEqual(strKey.key() as StrKey, .test) + XCTAssertEqual(try strKey.key() as StrKey, .test) enum IntKey: Int, CodingKey { case test = 1 @@ -51,7 +51,7 @@ class AnyCodingKeyTests: XCTestCase { let intKey = AnyCodingKey(IntKey.test) XCTAssertEqual(intKey.stringValue, "1") XCTAssertEqual(intKey.intValue, 1) - XCTAssertEqual(intKey.key() as IntKey, .test) + XCTAssertEqual(try intKey.key() as IntKey, .test) } }