From ea7c33fe8ef5dbcd6e9f8c1dd7d671b0eace1e47 Mon Sep 17 00:00:00 2001 From: Yaroslav Panasenko Date: Mon, 2 Mar 2020 19:06:47 +0200 Subject: [PATCH] Added xdr decoding --- Example/Podfile.lock | 4 +- Example/Tests/XDRDecodingTests.swift | 199 ++++++++++++++++++ Example/Tests/XDREncodingTests.swift | 19 +- .../TokenDWallet.xcodeproj/project.pbxproj | 4 + Sources/TokenDWallet/Xdr/LedgerHeader.swift | 8 +- .../Xdr/Lib/Errors/XDRErrors.swift | 2 + .../TokenDWallet/Xdr/Lib/XDRArrayFixed.swift | 51 +++-- Sources/TokenDWallet/Xdr/Lib/XDRCodable.swift | 10 + .../Xdr/Lib/XDRDiscriminatedUnion.swift | 2 +- Sources/TokenDWallet/Xdr/Lib/XDREnum.swift | 8 +- 10 files changed, 277 insertions(+), 30 deletions(-) create mode 100644 Example/Tests/XDRDecodingTests.swift diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 838126c..fd4e18c 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -20,8 +20,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: DLCryptoKit: 491c522583e92c90c09c06bf1192ef741022af33 DLOpenSSL: 78d928228cb6d95fcaa354f4f9aba041ccf95457 - TokenDWallet: c66abb4a51c2c035ea439e27cf64e8589773a63f + TokenDWallet: a740f7767f3e66566e4a507f351a2bbf5901aa22 PODFILE CHECKSUM: 2ff703f484f93022caae12590846b6530f3c4419 -COCOAPODS: 1.6.1 +COCOAPODS: 1.8.0 diff --git a/Example/Tests/XDRDecodingTests.swift b/Example/Tests/XDRDecodingTests.swift new file mode 100644 index 0000000..5030637 --- /dev/null +++ b/Example/Tests/XDRDecodingTests.swift @@ -0,0 +1,199 @@ +import XCTest +@testable import TokenDWallet + +// swiftlint:disable identifier_name + +class XDRDecodingTests: XCTestCase { + + func testBasicTypes() { + // Int + XCTAssertEqual(try! Int32(xdrBase64: "AAAABQ=="), Int32(5)) + XCTAssertEqual(try! UInt32(xdrBase64: "AAAABQ=="), UInt32(5)) + XCTAssertEqual(try! UInt64(xdrBase64: "AAAAAAAAAAU="), UInt64(5)) + XCTAssertEqual(try! Int64(xdrBase64: "AAAAAAAAAAU="), Int64(5)) + + // Float, Double and Quadruple is unsupported + + // Bool + XCTAssertEqual(try! Bool(xdrBase64: "AAAAAQ=="), true) + } + + func testEnum() { + enum TestEnum: Int32, XDREnum { + case a = 0 + case b = 1 + case c = -1 + } + + XCTAssertEqual(try! TestEnum(xdrBase64: "AAAAAA=="), TestEnum.a) + XCTAssertEqual(try! TestEnum(xdrBase64: "AAAAAQ=="), TestEnum.b) + XCTAssertEqual(try! TestEnum(xdrBase64: "/////w=="), TestEnum.c) + } + + func testOptional() { + var test: Int32? + XCTAssertEqual(decodeOptionalInt32(xdrBase64: "AAAAAA=="), test) + test = 5 + XCTAssertEqual(decodeOptionalInt32(xdrBase64: "AAAAAQAAAAU="), test) + } + + private func decodeOptionalInt32(xdrBase64: String) -> Int32? { + var data = Data(base64Encoded: xdrBase64)! + if (try! Bool(xdrData: &data)) { + return try! Int32(xdrData: &data) + } else { + return nil + } + } + + func testString() { + XCTAssertEqual(try! String(xdrBase64: "AAAABHRlc3Q="), "test") + } + + func testOpaque() { + // Dinamic size + let data = Data(bytes: [1]) + XCTAssertEqual(try! Data(xdrBase64: "AAAAAQE="), data) + // Fixed size + struct XDRDataFixed1: XDRDataFixed { + static var length: Int { return 1 } + + var wrapped: Data + + init() { + self.wrapped = Data() + } + } + XCTAssertEqual(try! XDRDataFixed1(xdrBase64: "AQ=="), try! XDRDataFixed1(data)) + } + + func testArray() { + // Dinamic size + let data = [Int64(1)] + XCTAssertEqual(try! decodeArrayInt64(xdrBase64: "AAAAAQAAAAAAAAAB"), data) + struct XDRArrayFixed1: XDRArrayFixed { + typealias Element = WrappedElement + + static var length: Int { return 1 } + + var wrapped: [WrappedElement] + + init() { + self.wrapped = [WrappedElement]() + } + } + // Fixed size + XCTAssertEqual(try! XDRArrayFixed1(xdrBase64: "AAAAAAAAAAE=").wrapped, try! XDRArrayFixed1(data).wrapped) + } + + private func decodeArrayInt64(xdrBase64: String) throws -> [Int64] { + var data = Data(base64Encoded: xdrBase64)! + let length = try Int32(xdrData: &data) + var result = [Int64]() + for _ in 1...length { + result.append(try Int64(xdrData: &data)) + } + + return result + } + + func testDescriminatedUnion() { + enum TestDescriminatedUnion: XDRDiscriminatedUnion, XDRDecodable { + case a(Int64) + case b(Int32) + case c + + var discriminant: Int32 { + switch self { + case .a: return 0 + case .b: return 1 + case .c: return 2 + } + } + + func toXDR() -> Data { + var xdr = Data() + + xdr.append(self.discriminant.toXDR()) + + switch self { + case .a(let data): xdr.append(data.toXDR()) + case .b(let data): xdr.append(data.toXDR()) + case .c: xdr.append(Data()) + } + + return xdr + } + + init(xdrData: inout Data) throws { + let discriminant = try Int32(xdrData: &xdrData) + + switch discriminant { + case 0: + let data = try Int64(xdrData: &xdrData) + self = .a(data) + case 1: + let data = try Int32(xdrData: &xdrData) + self = .b(data) + case 2: + self = .c + default: + throw XDRErrors.unknownEnumCase + } + } + } + + XCTAssertEqual(try! TestDescriminatedUnion(xdrBase64: "AAAAAAAAAAAAAAAB").toXdrBase64String(), TestDescriminatedUnion.a(1).toXdrBase64String()) + XCTAssertEqual(try! TestDescriminatedUnion(xdrBase64: "AAAAAQAAAAE=").toXdrBase64String(), TestDescriminatedUnion.b(1).toXdrBase64String()) + XCTAssertEqual(try! TestDescriminatedUnion(xdrBase64: "AAAAAg==").toXdrBase64String(), TestDescriminatedUnion.c.toXdrBase64String()) + } + + func testStruct() { + enum TestEnum: Int32, XDREnum { + case a = 0 + case b = 1 + case c = -1 + } + struct Test: XDRCodable { + var a: Int32 + var b: Int32? + var c: Bool + var d: TestEnum + + func toXDR() -> Data { + var xdr = Data() + + xdr.append(self.a.toXDR()) + xdr.append(self.b.toXDR()) + xdr.append(self.c.toXDR()) + xdr.append(self.d.toXDR()) + + return xdr + } + + init(a: Int32, b: Int32?, c: Bool, d: TestEnum) { + self.a = a + self.b = b + self.c = c + self.d = d + } + + init(xdrData: inout Data) throws { + self.a = try Int32(xdrData: &xdrData) + if (try Bool(xdrData: &xdrData)) { + self.b = try Int32(xdrData: &xdrData) + } else { + self.b = nil + } + self.c = try Bool(xdrData: &xdrData) + self.d = try TestEnum(xdrData: &xdrData) + } + } + + let test = Test(a: 1, b: nil, c: true, d: TestEnum.a) + + XCTAssertEqual(try! Test(xdrBase64: "AAAAAQAAAAAAAAABAAAAAA==").toXdrBase64String(), test.toXdrBase64String()) + } +} + +// swiftlint:enable identifier_name diff --git a/Example/Tests/XDREncodingTests.swift b/Example/Tests/XDREncodingTests.swift index 06d9280..7ba31fc 100644 --- a/Example/Tests/XDREncodingTests.swift +++ b/Example/Tests/XDREncodingTests.swift @@ -25,9 +25,9 @@ class XDREncodingTests: XCTestCase { case c = -1 } - XCTAssertEqual(TestEnum.a.rawValue.toXDR().base64, "AAAAAA==") - XCTAssertEqual(TestEnum.b.rawValue.toXDR().base64, "AAAAAQ==") - XCTAssertEqual(TestEnum.c.rawValue.toXDR().base64, "/////w==") + XCTAssertEqual(TestEnum.a.toXDR().base64, "AAAAAA==") + XCTAssertEqual(TestEnum.b.toXDR().base64, "AAAAAQ==") + XCTAssertEqual(TestEnum.c.toXDR().base64, "/////w==") } func testOptional() { @@ -62,8 +62,19 @@ class XDREncodingTests: XCTestCase { // Dinamic size let data = [Int64(1)] XCTAssertEqual(data.toXDR().base64, "AAAAAQAAAAAAAAAB") + struct XDRArrayFixed1: XDRArrayFixed { + typealias Element = WrappedElement + + static var length: Int { return 1 } + + var wrapped: [WrappedElement] + + init() { + self.wrapped = [WrappedElement]() + } + } // Fixed size - XCTAssertEqual(XDRArrayFixed(data).toXDR().base64, "AAAAAAAAAAE=") + XCTAssertEqual(try! XDRArrayFixed1(data).toXDR().base64, "AAAAAAAAAAE=") } func testDescriminatedUnion() { diff --git a/Example/TokenDWallet.xcodeproj/project.pbxproj b/Example/TokenDWallet.xcodeproj/project.pbxproj index 9327000..43cf6b1 100644 --- a/Example/TokenDWallet.xcodeproj/project.pbxproj +++ b/Example/TokenDWallet.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 7D8C0B06FEDBFDD4F6A860AA /* libPods-TokenDWallet_Tests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 46DC2F103B0418D6E33CA14A /* libPods-TokenDWallet_Tests.a */; }; 7FABEA6F20FF4EE30005E751 /* ECDSASignDecoratedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABEA6E20FF4EE30005E751 /* ECDSASignDecoratedTests.swift */; }; 7FABEA7120FF54E60005E751 /* TransactionModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FABEA7020FF54E60005E751 /* TransactionModelTests.swift */; }; + C47C788D240D75900068C152 /* XDRDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47C788C240D75900068C152 /* XDRDecodingTests.swift */; }; C4C0EF6B2094B57700DAC6C4 /* XDREncodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0EF6A2094B57700DAC6C4 /* XDREncodingTests.swift */; }; C4C0EF7F2097053200DAC6C4 /* Base32CheckTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0EF7D209704D300DAC6C4 /* Base32CheckTests.swift */; }; C4C0EFAC20971CDC00DAC6C4 /* XDRTypesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C0EFAA20971CB400DAC6C4 /* XDRTypesTests.swift */; }; @@ -47,6 +48,7 @@ 7FABEA6E20FF4EE30005E751 /* ECDSASignDecoratedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ECDSASignDecoratedTests.swift; sourceTree = ""; }; 7FABEA7020FF54E60005E751 /* TransactionModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TransactionModelTests.swift; sourceTree = ""; }; 9A7D0DDACE2712C49B8FB4B1 /* Pods-TokenDWallet_Tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-TokenDWallet_Tests.release.xcconfig"; path = "Pods/Target Support Files/Pods-TokenDWallet_Tests/Pods-TokenDWallet_Tests.release.xcconfig"; sourceTree = ""; }; + C47C788C240D75900068C152 /* XDRDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDRDecodingTests.swift; sourceTree = ""; }; C4C0EF6A2094B57700DAC6C4 /* XDREncodingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = XDREncodingTests.swift; sourceTree = ""; }; C4C0EF7D209704D300DAC6C4 /* Base32CheckTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Base32CheckTests.swift; sourceTree = ""; }; C4C0EFAA20971CB400DAC6C4 /* XDRTypesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XDRTypesTests.swift; sourceTree = ""; }; @@ -121,6 +123,7 @@ 7FABEA6E20FF4EE30005E751 /* ECDSASignDecoratedTests.swift */, 7FABEA7020FF54E60005E751 /* TransactionModelTests.swift */, C4C0EF6A2094B57700DAC6C4 /* XDREncodingTests.swift */, + C47C788C240D75900068C152 /* XDRDecodingTests.swift */, C4C0EFAA20971CB400DAC6C4 /* XDRTypesTests.swift */, 607FACE91AFB9204008FA782 /* Supporting Files */, ); @@ -364,6 +367,7 @@ buildActionMask = 2147483647; files = ( 7FABEA7120FF54E60005E751 /* TransactionModelTests.swift in Sources */, + C47C788D240D75900068C152 /* XDRDecodingTests.swift in Sources */, C4C0EF7F2097053200DAC6C4 /* Base32CheckTests.swift in Sources */, C4C0EFAC20971CDC00DAC6C4 /* XDRTypesTests.swift in Sources */, C4C0EF6B2094B57700DAC6C4 /* XDREncodingTests.swift in Sources */, diff --git a/Sources/TokenDWallet/Xdr/LedgerHeader.swift b/Sources/TokenDWallet/Xdr/LedgerHeader.swift index 4764359..c4e983d 100644 --- a/Sources/TokenDWallet/Xdr/LedgerHeader.swift +++ b/Sources/TokenDWallet/Xdr/LedgerHeader.swift @@ -52,7 +52,7 @@ public struct LedgerHeader: XDREncodable { public var baseReserve: Uint32 public var maxTxSetSize: Uint32 public var txExpirationPeriod: Int64 - public var skipList: XDRArrayFixed +// public var skipList: XDRArrayFixed public var ext: LedgerHeaderExt public init( @@ -67,7 +67,7 @@ public struct LedgerHeader: XDREncodable { baseReserve: Uint32, maxTxSetSize: Uint32, txExpirationPeriod: Int64, - skipList: XDRArrayFixed, +// skipList: XDRArrayFixed, ext: LedgerHeaderExt) { self.ledgerVersion = ledgerVersion @@ -81,7 +81,7 @@ public struct LedgerHeader: XDREncodable { self.baseReserve = baseReserve self.maxTxSetSize = maxTxSetSize self.txExpirationPeriod = txExpirationPeriod - self.skipList = skipList +// self.skipList = skipList self.ext = ext } @@ -99,7 +99,7 @@ public struct LedgerHeader: XDREncodable { xdr.append(self.baseReserve.toXDR()) xdr.append(self.maxTxSetSize.toXDR()) xdr.append(self.txExpirationPeriod.toXDR()) - xdr.append(self.skipList.toXDR()) +// xdr.append(self.skipList.toXDR()) xdr.append(self.ext.toXDR()) return xdr diff --git a/Sources/TokenDWallet/Xdr/Lib/Errors/XDRErrors.swift b/Sources/TokenDWallet/Xdr/Lib/Errors/XDRErrors.swift index 98cae0c..f17a485 100644 --- a/Sources/TokenDWallet/Xdr/Lib/Errors/XDRErrors.swift +++ b/Sources/TokenDWallet/Xdr/Lib/Errors/XDRErrors.swift @@ -3,4 +3,6 @@ import Foundation public enum XDRErrors: Swift.Error { case wrongDataLength case decodingError + case base64DecodeError + case unknownEnumCase } diff --git a/Sources/TokenDWallet/Xdr/Lib/XDRArrayFixed.swift b/Sources/TokenDWallet/Xdr/Lib/XDRArrayFixed.swift index b70708e..da38917 100644 --- a/Sources/TokenDWallet/Xdr/Lib/XDRArrayFixed.swift +++ b/Sources/TokenDWallet/Xdr/Lib/XDRArrayFixed.swift @@ -1,40 +1,57 @@ import Foundation -public struct XDRArrayFixed: Sequence { - public private(set) var list: [T] +public protocol XDRArrayFixed: XDRCodable, Sequence where Element: XDRCodable { + static var length: Int { get } + var wrapped: [Element] { get set } - public init(_ array: [T]) { - self.list = array + init() + init(_ array: [Element]) throws +} + +extension XDRArrayFixed { + public init(_ array: [Element]) throws { + self.init() + + if array.count > Self.length { + throw XDRErrors.wrongDataLength + } + + self.wrapped = array } - public subscript(_ index: Int) -> T { - return list[index] + public init(xdrData: inout Data) throws { + self.init() + for _ in 1...Self.length { + self.wrapped.append(try Element(xdrData: &xdrData)) + } + } + + public subscript(_ index: Int) -> Element { + return wrapped[index] } - public func makeIterator() -> AnyIterator { + public func makeIterator() -> AnyIterator { var index = 0 - + return AnyIterator { - let element: T? = index < self.list.count ? self[index] : nil + let element: Element? = index < self.wrapped.count ? self[index] : nil index += 1 - + return element } } -} - -extension XDRArrayFixed: XDREncodable, CustomDebugStringConvertible { + public func toXDR() -> Data { var xdr = Data() - + forEach { xdr.append($0.toXDR()) } - + return xdr } - + public var debugDescription: String { - return list.debugDescription + return wrapped.debugDescription } } diff --git a/Sources/TokenDWallet/Xdr/Lib/XDRCodable.swift b/Sources/TokenDWallet/Xdr/Lib/XDRCodable.swift index c2668f0..daf2b1d 100644 --- a/Sources/TokenDWallet/Xdr/Lib/XDRCodable.swift +++ b/Sources/TokenDWallet/Xdr/Lib/XDRCodable.swift @@ -14,6 +14,16 @@ extension XDREncodable { public protocol XDRDecodable { init(xdrData: inout Data) throws + init(xdrBase64: String) throws +} + +extension XDRDecodable { + public init(xdrBase64: String) throws { + guard var data = Data(base64Encoded: xdrBase64) else { + throw XDRErrors.base64DecodeError + } + try self.init(xdrData: &data) + } } public typealias XDRCodable = XDREncodable & XDRDecodable diff --git a/Sources/TokenDWallet/Xdr/Lib/XDRDiscriminatedUnion.swift b/Sources/TokenDWallet/Xdr/Lib/XDRDiscriminatedUnion.swift index d31da90..3ea8bbc 100644 --- a/Sources/TokenDWallet/Xdr/Lib/XDRDiscriminatedUnion.swift +++ b/Sources/TokenDWallet/Xdr/Lib/XDRDiscriminatedUnion.swift @@ -1,5 +1,5 @@ import Foundation -public protocol XDRDiscriminatedUnion: XDREncodable { +public protocol XDRDiscriminatedUnion: XDRCodable { var discriminant: Int32 { get } } diff --git a/Sources/TokenDWallet/Xdr/Lib/XDREnum.swift b/Sources/TokenDWallet/Xdr/Lib/XDREnum.swift index aee2448..76f2294 100644 --- a/Sources/TokenDWallet/Xdr/Lib/XDREnum.swift +++ b/Sources/TokenDWallet/Xdr/Lib/XDREnum.swift @@ -1,9 +1,13 @@ import Foundation -public protocol XDREnum: RawRepresentable, XDREncodable, CaseIterable { } +public protocol XDREnum: RawRepresentable, XDRCodable, CaseIterable { } -extension XDREnum where RawValue: XDREncodable { +extension XDREnum where RawValue: XDRCodable { public func toXDR() -> Data { return self.rawValue.toXDR() } + + public init(xdrData: inout Data) throws { + self.init(rawValue: try RawValue(xdrData: &xdrData))! + } }