Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Sources/CBORDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public class CBORDecoder {
}
}

func getDateFromTimestamp(_ item: CBOR) throws -> Date {
public func getDateFromTimestamp(_ item: CBOR) throws -> Date {
switch item {
case .double(let d):
return Date(timeIntervalSince1970: TimeInterval(d))
Expand Down
2 changes: 1 addition & 1 deletion Sources/Decoder/CodableCBORDecoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ extension _CBORDecoder: Decoder {
}
}

protocol CBORDecodingContainer: AnyObject {
public protocol CBORDecodingContainer: AnyObject {
var codingPath: [CodingKey] { get set }

var userInfo: [CodingUserInfoKey : Any] { get }
Expand Down
3 changes: 2 additions & 1 deletion Sources/Decoder/KeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ extension _CBORDecoder {
for _ in 0..<count {
guard let keyContainer = iterator.next() as? _CBORDecoder.SingleValueContainer,
let container = iterator.next() else {
fatalError() // FIXME
continue
// fatalError() // FIXME
}

let keyVal: AnyCodingKey
Expand Down
28 changes: 18 additions & 10 deletions Sources/Decoder/UnkeyedDecodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,23 @@ extension _CBORDecoder {

var currentIndex: Int = 0

lazy var nestedContainers: [CBORDecodingContainer] = {
lazy var nestedContainers: [CBORDecodingContainer?] = {
guard let count = self.count else {
return []
}

var nestedContainers: [CBORDecodingContainer] = []
var nestedContainers: [CBORDecodingContainer?] = []

do {
for _ in 0..<count {
for _ in 0..<count {
do {
let container = try self.decodeContainer()
nestedContainers.append(container)
} catch {
print("<SwiftCBOR> ERROR: \(error)") // FIXME
nestedContainers.append(nil)
}
} catch {
fatalError("\(error)") // FIXME
}

self.currentIndex = 0

return nestedContainers
Expand Down Expand Up @@ -125,7 +126,10 @@ extension _CBORDecoder.UnkeyedContainer: UnkeyedDecodingContainer {
try checkCanDecodeValue()
defer { self.currentIndex += 1 }

let container = self.nestedContainers[self.currentIndex]
guard let container = self.nestedContainers[self.currentIndex] else {
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Failed to decode \(T.self)")
}

let decoder = CodableCBORDecoder()
decoder.setOptions(self.options)
let value = try decoder.decode(T.self, from: container.data)
Expand Down Expand Up @@ -219,8 +223,12 @@ extension _CBORDecoder.UnkeyedContainer {
}
return container
case 0xc0:
throw DecodingError.dataCorruptedError(in: self, debugDescription: "Handling text-based date/time is not supported yet")
// Tagged value (epoch-baed date/time)
// Tagged value (epoch-baed date/time)
print("<SwiftCBOR> ERROR: Handling text-based date/time is not supported yet")
// throw DecodingError.dataCorruptedError(in: self, debugDescription: "Handling text-based date/time is not supported yet")
// TODO: handle as c1 yet
length = try getLengthOfItem(format: try self.peekByte(), startIndex: startIndex.advanced(by: 1)) + 1

case 0xc1:
length = try getLengthOfItem(format: try self.peekByte(), startIndex: startIndex.advanced(by: 1)) + 1
case 0xc2...0xdb:
Expand Down
14 changes: 10 additions & 4 deletions Sources/Encoder/KeyedEncodingContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Foundation

extension _CBOREncoder {
final class KeyedContainer<Key: CodingKey> {
var storage: [AnyCodingKey: CBOREncodingContainer] = [:]
var storage: [(AnyCodingKey, CBOREncodingContainer)] = []

var codingPath: [CodingKey]
var userInfo: [CodingUserInfoKey: Any]
Expand Down Expand Up @@ -38,7 +38,9 @@ extension _CBOREncoder.KeyedContainer: KeyedEncodingContainerProtocol {
userInfo: self.userInfo,
options: self.options
)
self.storage[anyCodingKeyForKey(key)] = container
let codingKey = anyCodingKeyForKey(key)
self.storage = self.storage.filter { $0.0 != codingKey }
self.storage.append( (codingKey, container) )
return container
}

Expand All @@ -48,7 +50,9 @@ extension _CBOREncoder.KeyedContainer: KeyedEncodingContainerProtocol {
userInfo: self.userInfo,
options: self.options
)
self.storage[anyCodingKeyForKey(key)] = container
let codingKey = anyCodingKeyForKey(key)
self.storage = self.storage.filter { $0.0 != codingKey }
self.storage.append( (codingKey, container) )
return container
}

Expand All @@ -58,7 +62,9 @@ extension _CBOREncoder.KeyedContainer: KeyedEncodingContainerProtocol {
userInfo: self.userInfo,
options: self.options
)
self.storage[anyCodingKeyForKey(key)] = container
let codingKey = anyCodingKeyForKey(key)
self.storage = self.storage.filter { $0.0 != codingKey }
self.storage.append( (codingKey, container) )
return KeyedEncodingContainer(container)
}

Expand Down
148 changes: 148 additions & 0 deletions Tests/CBORDateTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import XCTest
import Foundation
@testable import SwiftCBOR

class CBORDateTests: XCTestCase {

struct CustomAndBool: Decodable {
let date: CustomDataDecodable
let bool: Bool

enum CodingKeys: Int, CodingKey {
case date = 0
case bool = 3
}
}

struct DateAndBool: Codable {
let date: Date
let bool: Bool

enum CodingKeys: Int, CodingKey {
case date = 0
case bool = 3
}
}

struct BoolOnly: Codable {
let bool: Bool

enum CodingKeys: Int, CodingKey {
case bool = 3
}
}

func testDecodeDate_C1_and_Date() {
let testData = Data(hex: "A2 00C11A682C4A77 03F5"
.replacingOccurrences(of: " ", with: ""))!
let decodedStruct = try! CodableCBORDecoder().decode(DateAndBool.self, from: testData)
XCTAssertNotNil(decodedStruct)
}

func testDecodeDate_C0_noFatalError() {
let testData = Data(hex: "A4 00C01A682C4A77 01C01A682C4A77 02C01A682C4A77 03F5"
.replacingOccurrences(of: " ", with: ""))!
do {
let _ = try CodableCBORDecoder().decode(DateAndBool.self, from: testData)
XCTFail("Must throw an error instead of fatalError")
} catch {
XCTAssertNotNil(error)
}
}

func testDecodeDate_C0_and_bool_noFatalErrorForPartialModel() {
let testData = Data(hex: "A4 00C01A682C4A77 01C01A682C4A77 02C01A682C4A77 03F5"
.replacingOccurrences(of: " ", with: ""))!
let decodedStruct = try? CodableCBORDecoder().decode(BoolOnly.self, from: testData)
XCTAssertNotNil(decodedStruct)
}

func testDecodeDate_C0_custom_epoch() {
//
let testData = Data(hex: "A4 00C01A682C4A7701C 01A682C4A77 02C01A682C4A77 03F5"
.replacingOccurrences(of: " ", with: ""))!
let decodedStruct = try? CodableCBORDecoder().decode(CustomAndBool.self, from: testData)
XCTAssertNotNil(decodedStruct?.date)
XCTAssertNotNil(decodedStruct)
XCTAssertNotNil(decodedStruct?.date.date)
}

func testDecodeDate_C0_custom_string() {
for formatter in [CustomDataDecodable.dateTimeWithMillisFormatter, CustomDataDecodable.dateTimeFormatter, CustomDataDecodable.onlyDateFormatter] {
let isoDate = formatter.string(from: Date())
let cbor = CBOR.tagged(CBOR.Tag.standardDateTimeString, .utf8String(isoDate))
let c0DateString = Data(cbor.encode()).hex

let testData = Data(hex: "A2 00 \(c0DateString) 03F5"
.replacingOccurrences(of: " ", with: ""))!

let decodedStruct = try? CodableCBORDecoder().decode(CustomAndBool.self, from: testData)
XCTAssertNotNil(decodedStruct?.date.date)

// just check that no any fatal error
let wrongDecodedStruct = try? CodableCBORDecoder().decode(DateAndBool.self, from: testData)
XCTAssertNil(wrongDecodedStruct)
}
}
}

/// it decodes C0 and C1 dates
struct CustomDataDecodable: Decodable {
let date: Date
init(from decoder: Decoder) throws {
let container = (try decoder.singleValueContainer()) as? CBORDecodingContainer
guard let arrayData = container?.data else {
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "can't cast to `CBORDecodingContainer`"))
}
self.date = try Self.dateFromCBORData(Data(arrayData))
}

static func dateFromCBORData(_ data: Data) throws -> Date {
let c0 = 0xC0 + CBOR.Tag.standardDateTimeString.rawValue
let c1 = 0xC0 + CBOR.Tag.epochBasedDateTime.rawValue
if data.count > 1, data[0] == c0 || data[0] == c1 { // 0xC0 or 0xC1
let timeData = data.suffix(from: 1)
let item = try CBOR.decode([UInt8](timeData))

if let item {
if let date = try? getDateFromTimestamp(item) {
return date
} else if case .utf8String(let string) = item {
for formatter in [Self.dateTimeWithMillisFormatter, Self.dateTimeFormatter, Self.onlyDateFormatter] {
if let date = formatter.date(from: string) {
return date
}
}
}
}
}
throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: [], debugDescription: "CBOR Data to Date decode failed"))
}
}

extension CustomDataDecodable {

// 2017-01-23T10:12:31.484Z
static let dateTimeWithMillisFormatter: ISO8601DateFormatter = {
let v = ISO8601DateFormatter()
v.timeZone = TimeZone(identifier: "UTC")
v.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
return v
}()

// 2021-06-30T19:22:31Z
static let dateTimeFormatter: ISO8601DateFormatter = {
let v = ISO8601DateFormatter()
v.timeZone = TimeZone(identifier: "UTC")
v.formatOptions = [.withInternetDateTime]
return v
}()

// 2016-06-13
static let onlyDateFormatter: ISO8601DateFormatter = {
let v = ISO8601DateFormatter()
v.timeZone = TimeZone(identifier: "UTC")
v.formatOptions = .withFullDate
return v
}()
}
1 change: 0 additions & 1 deletion Tests/CodableCBOREncoderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ class CodableCBOREncoderTests: XCTestCase {

XCTAssert(
encoded == [0xa2, 0x63, 0x61, 0x67, 0x65, 0x18, 0x1b, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x63, 0x48, 0x61, 0x6d]
|| encoded == [0xa2, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x63, 0x48, 0x61, 0x6d, 0x63, 0x61, 0x67, 0x65, 0x18, 0x1b]
)
}
}
Expand Down