diff --git a/Source/AwsCommonRuntimeKit/crt/CBOR.swift b/Source/AwsCommonRuntimeKit/crt/CBOR.swift index 25ce3b64..ada5f3b5 100644 --- a/Source/AwsCommonRuntimeKit/crt/CBOR.swift +++ b/Source/AwsCommonRuntimeKit/crt/CBOR.swift @@ -63,20 +63,40 @@ public class CBOREncoder { /// Encode a single type /// - Parameters: - /// - value: value to encode + /// - value: value to encode /// - Throws: CommonRuntimeError.crtError public func encode(_ value: CBORType) { switch value { - case .uint(let value): aws_cbor_encoder_write_uint(self.rawValue, value) + case .uint(let value): + aws_cbor_encoder_write_uint(self.rawValue, value) case .int(let value): - do { - if value >= 0 { - aws_cbor_encoder_write_uint(self.rawValue, UInt64(value)) - } else { - aws_cbor_encoder_write_negint(self.rawValue, UInt64(-1 - value)) - } + if value >= 0 { + aws_cbor_encoder_write_uint(self.rawValue, UInt64(value)) + } else { + aws_cbor_encoder_write_negint(self.rawValue, UInt64(-1 - value)) } - case .double(let value): aws_cbor_encoder_write_float(self.rawValue, value) + case .double(let value): + aws_cbor_encoder_write_float(self.rawValue, value) + case .bool(let value): + aws_cbor_encoder_write_bool(self.rawValue, value) + case .bytes(let data): + data.withAWSByteCursorPointer { cursor in + aws_cbor_encoder_write_bytes(self.rawValue, cursor.pointee) + } + case .text(let string): + string.withByteCursor { cursor in + aws_cbor_encoder_write_text(self.rawValue, cursor) + } + case .null: + aws_cbor_encoder_write_null(self.rawValue) + case .undefined: + aws_cbor_encoder_write_undefined(self.rawValue) + case .date(let date): + aws_cbor_encoder_write_tag(self.rawValue, UInt64(AWS_CBOR_TAG_EPOCH_TIME)) + aws_cbor_encoder_write_float(self.rawValue, date.timeIntervalSince1970) + case .tag(let tag): + aws_cbor_encoder_write_tag(self.rawValue, tag) + case .array(let values): do { aws_cbor_encoder_write_array_start(self.rawValue, values.count) @@ -84,13 +104,6 @@ public class CBOREncoder { encode(value) } } - case .bool(let value): aws_cbor_encoder_write_bool(self.rawValue, value) - case .bytes(let value): - do { - value.withAWSByteCursorPointer { cursor in - aws_cbor_encoder_write_bytes(self.rawValue, cursor.pointee) - } - } case .map(let values): do { aws_cbor_encoder_write_map_start(self.rawValue, values.count) @@ -99,20 +112,7 @@ public class CBOREncoder { encode(value) } } - case .null: aws_cbor_encoder_write_null(self.rawValue) - case .text(let value): - do { - value.withByteCursor { cursor in - aws_cbor_encoder_write_text(self.rawValue, cursor) - } - } - case .date(let value): - do { - aws_cbor_encoder_write_tag(self.rawValue, UInt64(AWS_CBOR_TAG_EPOCH_TIME)) - aws_cbor_encoder_write_float(self.rawValue, value.timeIntervalSince1970) - } - case .undefined: aws_cbor_encoder_write_undefined(self.rawValue) - case .tag(let value): aws_cbor_encoder_write_tag(self.rawValue, value) + case .indef_break: aws_cbor_encoder_write_break(self.rawValue) case .indef_array_start: aws_cbor_encoder_write_indef_array_start(self.rawValue) case .indef_map_start: aws_cbor_encoder_write_indef_map_start(self.rawValue) @@ -132,6 +132,7 @@ public class CBOREncoder { } } +// swiftlint:disable type_body_length /// Decoder for the CBOR encoding. public class CBORDecoder { var rawValue: OpaquePointer @@ -151,218 +152,265 @@ public class CBORDecoder { self.rawValue = rawValue } - // swiftlint:disable function_body_length - /// Decodes and returns the next value. If there is no value, this function will throw an error. + /// Returns true if there is any data left to decode. + public func hasNext() -> Bool { + aws_cbor_decoder_get_remaining_length(self.rawValue) != 0 + } + + /// Decodes and returns the next value. If there is no value, this function will throw an error. /// You must call `hasNext()` before calling this function. public func popNext() throws -> CBORType { var cbor_type: aws_cbor_type = AWS_CBOR_TYPE_UNKNOWN guard aws_cbor_decoder_peek_type(self.rawValue, &cbor_type) == AWS_OP_SUCCESS else { throw CommonRunTimeError.crtError(.makeFromLastError()) } + switch cbor_type { case AWS_CBOR_TYPE_UINT: - do { - var out_value: UInt64 = 0 - guard - aws_cbor_decoder_pop_next_unsigned_int_val(self.rawValue, &out_value) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - return .uint(out_value) - } - + return try decodeUInt() case AWS_CBOR_TYPE_NEGINT: - do { - var out_value: UInt64 = 0 - guard - aws_cbor_decoder_pop_next_negative_int_val(self.rawValue, &out_value) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - guard out_value <= Int64.max else { - throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) - } - return .int(Int64(-Int64(out_value) - 1)) - } + return try decodeNegInt() case AWS_CBOR_TYPE_FLOAT: - do { - var out_value: Double = 0 - guard - aws_cbor_decoder_pop_next_float_val(self.rawValue, &out_value) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - return .double(out_value) - } + return try decodeFloat() case AWS_CBOR_TYPE_BYTES: - do { - var out_value: aws_byte_cursor = aws_byte_cursor() - guard - aws_cbor_decoder_pop_next_bytes_val(self.rawValue, &out_value) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - return .bytes(out_value.toData()) - } + return try decodeBytes() case AWS_CBOR_TYPE_TEXT: - do { - var out_value: aws_byte_cursor = aws_byte_cursor() - guard - aws_cbor_decoder_pop_next_text_val(self.rawValue, &out_value) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - return .text(out_value.toString()) - } + return try decodeText() case AWS_CBOR_TYPE_BOOL: - do { - var out_value: Bool = false - guard - aws_cbor_decoder_pop_next_boolean_val(self.rawValue, &out_value) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - return .bool(out_value) - } + return try decodeBool() case AWS_CBOR_TYPE_NULL: - do { - guard - aws_cbor_decoder_consume_next_single_element(self.rawValue) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - return .null - } + return try decodeNull() case AWS_CBOR_TYPE_TAG: - var out_value: UInt64 = 0 - guard - aws_cbor_decoder_pop_next_tag_val(self.rawValue, &out_value) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - if out_value != 1 { - return .tag(out_value) - } - - let timestamp = try popNext() - - if case .double(let value) = timestamp { - return .date(Date.init(timeIntervalSince1970: value)) - } else if case .uint(let value) = timestamp { - return .date(Date.init(timeIntervalSince1970: Double(value))) - } else if case .int(let value) = timestamp { - return .date(Date.init(timeIntervalSince1970: Double(value))) - } else { - throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) - } + return try decodeTag() case AWS_CBOR_TYPE_ARRAY_START: - var out_value: UInt64 = 0 - guard - aws_cbor_decoder_pop_next_array_start(self.rawValue, &out_value) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) - } - var array: [CBORType] = [] - for _ in 0.. CBORType { + var out_value: UInt64 = 0 + guard aws_cbor_decoder_pop_next_unsigned_int_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .uint(out_value) + } + + private func decodeNegInt() throws -> CBORType { + var out_value: UInt64 = 0 + guard aws_cbor_decoder_pop_next_negative_int_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + guard out_value <= Int64.max else { + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + // CBOR negative integers are encoded as -1 - value, so convert accordingly. + return .int(Int64(-Int64(out_value) - 1)) + } + + private func decodeFloat() throws -> CBORType { + var out_value: Double = 0 + guard aws_cbor_decoder_pop_next_float_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .double(out_value) + } + + private func decodeBytes() throws -> CBORType { + var out_value = aws_byte_cursor() + guard aws_cbor_decoder_pop_next_bytes_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .bytes(out_value.toData()) + } + + private func decodeText() throws -> CBORType { + var out_value = aws_byte_cursor() + guard aws_cbor_decoder_pop_next_text_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .text(out_value.toString()) + } + + private func decodeBool() throws -> CBORType { + var out_value: Bool = false + guard aws_cbor_decoder_pop_next_boolean_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .bool(out_value) + } + + private func decodeNull() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .null + } + + private func decodeUndefined() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .undefined + } + + private func decodeTag() throws -> CBORType { + var out_value: UInt64 = 0 + guard aws_cbor_decoder_pop_next_tag_val(self.rawValue, &out_value) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + guard out_value == 1 else { + return .tag(out_value) + } + + let timestamp = try popNext() + switch timestamp { + case .double(let value): + return .date(Date(timeIntervalSince1970: value)) + case .uint(let value): + return .date(Date(timeIntervalSince1970: Double(value))) + case .int(let value): + return .date(Date(timeIntervalSince1970: Double(value))) + default: + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + } + + private func decodeDefiniteArray() throws -> CBORType { + var length: UInt64 = 0 + guard aws_cbor_decoder_pop_next_array_start(self.rawValue, &length) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + + var array: [CBORType] = [] + for _ in 0.. CBORType { + var out_value: UInt64 = 0 + guard + aws_cbor_decoder_pop_next_map_start(self.rawValue, &out_value) + == AWS_OP_SUCCESS + else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + var map: [String: CBORType] = [:] + for _ in 0.. CBORType { + // This should only be called inside indefinite decoding + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .indef_break + } + + private func decodeIndefiniteArray() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + var array: [CBORType] = [] + while true { + let cbor_type = try popNext() + if cbor_type == .indef_break { + break + } else { + array.append(cbor_type) + } + } + return .array(array) + } + + private func decodeIndefiniteMap() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + var map: [String: CBORType] = [:] + while true { + let keyVal = try popNext() + if keyVal == .indef_break { + break + } else { + guard case .text(let key) = keyVal else { + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) } - return .indef_bytes_start + + let value = try popNext() + map[key] = value } - case AWS_CBOR_TYPE_INDEF_TEXT_START: - do { - guard - aws_cbor_decoder_consume_next_single_element(self.rawValue) - == AWS_OP_SUCCESS - else { - throw CommonRunTimeError.crtError(.makeFromLastError()) + } + return .map(map) + } + + private func decodeIndefiniteBytes() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + + var data = Data() + while true { + let cbor_type = try popNext() + if cbor_type == .indef_break { + break + } else { + guard case .bytes(let chunkData) = cbor_type else { + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) } - return .indef_text_start + data.append(chunkData) } - default: - throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) } + return .bytes(data) } - /// Returns true if there is any data left to decode. - public func hasNext() -> Bool { - aws_cbor_decoder_get_remaining_length(self.rawValue) != 0 + private func decodeIndefiniteText() throws -> CBORType { + guard aws_cbor_decoder_consume_next_single_element(self.rawValue) == AWS_OP_SUCCESS else { + throw CommonRunTimeError.crtError(.makeFromLastError()) + } + + var text = "" + while true { + let cbor_type = try popNext() + if cbor_type == .indef_break { + break + } else { + guard case .text(let chunkStr) = cbor_type else { + throw CommonRunTimeError.crtError(CRTError(code: AWS_ERROR_CBOR_UNEXPECTED_TYPE.rawValue)) + } + text += chunkStr + } + } + return .text(text) } deinit { diff --git a/Test/AwsCommonRuntimeKitTests/crt/CBOR.swift b/Test/AwsCommonRuntimeKitTests/crt/CBOR.swift index 465417c5..44478d45 100644 --- a/Test/AwsCommonRuntimeKitTests/crt/CBOR.swift +++ b/Test/AwsCommonRuntimeKitTests/crt/CBOR.swift @@ -49,7 +49,8 @@ class CBORTests: XCBaseTestCase { .text("hello"), .indef_break, .indef_bytes_start, - .int(-100), + .bytes(Data([0x01, 0x02, 0x03])), // First chunk of bytes + .bytes(Data([0x04, 0x05])), // Second chunk of bytes .indef_break, ] let expected_decoded_values: [CBORType] = [ @@ -68,6 +69,7 @@ class CBORTests: XCBaseTestCase { // test tag .tag(0), .uint(100), + // test that tag 1 is decoded as date .date(Date(timeIntervalSince1970: 10.5)), .date(Date(timeIntervalSince1970: 20.5)), // complex types @@ -76,22 +78,10 @@ class CBORTests: XCBaseTestCase { .bytes("hello".data(using: .utf8)!), .text("hello"), // indef types - .indef_array_start, - .uint(100), - .int(-100), - .indef_break, - .indef_map_start, - .text("key1"), - .uint(100), - .text("key2"), - .int(-100), - .indef_break, - .indef_text_start, + .array([.uint(100), .int(-100)]), + .map(["key1": .uint(100), "key2": .int(-100)]), .text("hello"), - .indef_break, - .indef_bytes_start, - .int(-100), - .indef_break, + .bytes(Data([0x01, 0x02, 0x03, 0x04, 0x05])), ] @@ -107,9 +97,10 @@ class CBORTests: XCBaseTestCase { // decode the values let decoder = try! CBORDecoder(data: encoded) - for value in expected_decoded_values { + for expected in expected_decoded_values { XCTAssertTrue(decoder.hasNext()) - XCTAssertEqual(try! decoder.popNext(), value) + let actual = try! decoder.popNext() + XCTAssertEqual(actual, expected) } XCTAssertFalse(decoder.hasNext()) }