// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. import Foundation import Quick import Nimble @testable import SessionUtilitiesKit class BencodeSpec: QuickSpec { override class func spec() { // MARK: - Bencode describe("Bencode") { // MARK: -- when decoding context("when decoding") { // MARK: ---- should decode a basic string it("should decode a basic string") { let basicStringData: Data = "5:howdy".data(using: .utf8)! let result = try? Bencode.decode(String.self, from: basicStringData) expect(result).to(equal("howdy")) } // MARK: ---- should decode a basic integer it("should decode a basic integer") { let basicIntegerData: Data = "i3e".data(using: .utf8)! let result = try? Bencode.decode(Int.self, from: basicIntegerData) expect(result).to(equal(3)) } // MARK: ---- should decode a list of integers it("should decode a list of integers") { let basicIntListData: Data = "li1ei2ee".data(using: .utf8)! let result = try? Bencode.decode([Int].self, from: basicIntListData) expect(result).to(equal([1, 2])) } // MARK: ---- should decode a basic dict it("should decode a basic dict") { let basicDictData: Data = "d4:spaml1:a1:bee".data(using: .utf8)! let result = try? Bencode.decode([String: [String]].self, from: basicDictData) expect(result).to(equal(["spam": ["a", "b"]])) } // MARK: ---- decodes a decodable type it("decodes a decodable type") { let data: Data = "d8:intValuei100e11:stringValue4:Test".data(using: .utf8)! let result: TestType? = try? Bencode.decode(TestType.self, from: data) expect(result).to(equal(TestType(intValue: 100, stringValue: "Test"))) } // MARK: ---- decodes a stringified decodable type it("decodes a stringified decodable type") { let data: Data = "37:{\"intValue\":100,\"stringValue\":\"Test\"}".data(using: .utf8)! let result: TestType? = try? Bencode.decode(TestType.self, from: data) expect(result).to(equal(TestType(intValue: 100, stringValue: "Test"))) } } // MARK: -- when decoding a response context("when decoding a response") { // MARK: ---- with a decodable type context("with a decodable type") { // MARK: ------ decodes successfully it("decodes successfully") { let data: Data = "ld8:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) expect(result) .to(equal( BencodeResponse( info: TestType( intValue: 100, stringValue: "Test" ), data: Data([1, 2, 3, 4, 5]) ) )) } // MARK: ------ decodes successfully with no body it("decodes successfully with no body") { let data: Data = "ld8:intValuei100e11:stringValue4:Teste" .data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) expect(result) .to(equal( BencodeResponse( info: TestType( intValue: 100, stringValue: "Test" ), data: nil ) )) } // MARK: ------ throws a parsing error when given an invalid length it("throws a parsing error when given an invalid length") { let data: Data = "ld12:intValuei100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! expect { let result: BencodeResponse = try Bencode.decodeResponse(from: data) _ = result }.to(throwError(HTTPError.parsingFailed)) } // MARK: ------ throws a parsing error when given an invalid key it("throws a parsing error when given an invalid key") { let data: Data = "ld7:INVALIDi100e11:stringValue4:Teste5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! expect { let result: BencodeResponse = try Bencode.decodeResponse(from: data) _ = result }.to(throwError(HTTPError.parsingFailed)) } // MARK: ------ decodes correctly when trying to decode an int to a bool with custom handling it("decodes correctly when trying to decode an int to a bool with custom handling") { let data: Data = "ld9:boolValuei1e11:stringValue4:testee" .data(using: .utf8)! expect { let result: BencodeResponse = try Bencode.decodeResponse(from: data) _ = result }.toNot(throwError(HTTPError.parsingFailed)) } // MARK: ------ throws a parsing error when trying to decode an int to a bool it("throws a parsing error when trying to decode an int to a bool") { let data: Data = "ld9:boolValuei1e11:stringValue4:testee" .data(using: .utf8)! expect { let result: BencodeResponse = try Bencode.decodeResponse(from: data) _ = result }.to(throwError(HTTPError.parsingFailed)) } } // MARK: ---- with stringified json info context("with stringified json info") { // MARK: ------ decodes successfully it("decodes successfully") { let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) expect(result) .to(equal( BencodeResponse( info: TestType( intValue: 100, stringValue: "Test" ), data: Data([1, 2, 3, 4, 5]) ) )) } // MARK: ------ decodes successfully with no body it("decodes successfully with no body") { let data: Data = "l37:{\"intValue\":100,\"stringValue\":\"Test\"}e" .data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) expect(result) .to(equal( BencodeResponse( info: TestType( intValue: 100, stringValue: "Test" ), data: nil ) )) } // MARK: ------ throws a parsing error when invalid it("throws a parsing error when invalid") { let data: Data = "l36:{\"INVALID\":100,\"stringValue\":\"Test\"}5:\u{01}\u{02}\u{03}\u{04}\u{05}e" .data(using: .utf8)! expect { let result: BencodeResponse = try Bencode.decodeResponse(from: data) _ = result }.to(throwError(HTTPError.parsingFailed)) } } // MARK: ---- with a string value context("with a string value") { // MARK: ------ decodes successfully it("decodes successfully") { let data: Data = "l4:Test5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) expect(result) .to(equal( BencodeResponse( info: "Test", data: Data([1, 2, 3, 4, 5]) ) )) } // MARK: ------ decodes successfully with no body it("decodes successfully with no body") { let data: Data = "l4:Teste".data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) expect(result) .to(equal( BencodeResponse( info: "Test", data: nil ) )) } // MARK: ------ throws a parsing error when invalid it("throws a parsing error when invalid") { let data: Data = "l10:Teste".data(using: .utf8)! expect { let result: BencodeResponse = try Bencode.decodeResponse(from: data) _ = result }.to(throwError(HTTPError.parsingFailed)) } } // MARK: ---- with an int value context("with an int value") { // MARK: ------ decodes successfully it("decodes successfully") { let data: Data = "li100e5:\u{01}\u{02}\u{03}\u{04}\u{05}e".data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) expect(result) .to(equal( BencodeResponse( info: 100, data: Data([1, 2, 3, 4, 5]) ) )) } // MARK: ------ decodes successfully with no body it("decodes successfully with no body") { let data: Data = "li100ee".data(using: .utf8)! let result: BencodeResponse? = try? Bencode.decodeResponse(from: data) expect(result) .to(equal( BencodeResponse( info: 100, data: nil ) )) } // MARK: ------ throws a parsing error when invalid it("throws a parsing error when invalid") { let data: Data = "l4:Teste".data(using: .utf8)! expect { let result: BencodeResponse = try Bencode.decodeResponse(from: data) _ = result }.to(throwError(HTTPError.parsingFailed)) } } } } } } // MARK: - Test Types fileprivate struct TestType: Codable, Equatable { let intValue: Int let stringValue: String } fileprivate struct TestType2: Codable, Equatable { let stringValue: String let boolValue: Bool } fileprivate struct TestType3: Codable, Equatable { let stringValue: String let boolValue: Bool init(_ stringValue: String, _ boolValue: Bool) { self.stringValue = stringValue self.boolValue = boolValue } init(from decoder: Decoder) throws { let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) self = TestType3( try container.decode(String.self, forKey: .stringValue), ((try? container.decode(Bool.self, forKey: .boolValue)) ?? false) ) } }