// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved. // // stringlint:disable import Foundation // MARK: - BencodeDecoder public struct BencodeDecoder { public var userInfo: [CodingUserInfoKey: Any] public init(userInfo: [CodingUserInfoKey: Any] = [:], using dependencies: Dependencies = Dependencies()) { self.userInfo = userInfo self.userInfo[Dependencies.userInfoKey] = dependencies } public func decode(_ type: T.Type, from data: Data) throws -> T where T: Decodable { let decoder: _BencodeDecoder = _BencodeDecoder(data: data, userInfo: userInfo) return try T(from: decoder) } } // MARK: - AdditionalData Support public extension UnkeyedDecodingContainer { mutating func decodeAdditionalData(_ type: T.Type) throws -> T where T: Decodable, T: DataProtocol { let error: DecodingError = DecodingError.dataCorrupted( DecodingError.Context(codingPath: codingPath, debugDescription: "unable to decode additional data") ) switch (self) { case let bencodeUnkeyedContainer as _BencodeDecoder.UnkeyedContainer: guard let remainingData: Data = bencodeUnkeyedContainer.remainingData else { throw error } switch type { case is Data.Type: return try remainingData as? T ?? { throw error }() case is [UInt8].Type: return try Array(remainingData) as? T ?? { throw error }() default: throw error } default: switch type { case is Data.Type: return try self.decode(type) case is [UInt8].Type: return try Array(self.decode(Data.self)) as? T ?? { throw error }() default: throw error } } } } // MARK: - _BencodeDecodingContainer protocol _BencodeDecodingContainer: AnyObject { var codingPath: [CodingKey] { get set } var data: Data { get } var remainingData: Data? { get } } // MARK: - _BencodeDecoder final class _BencodeDecoder { var codingPath: [CodingKey] = [] var userInfo: [CodingUserInfoKey: Any] = [:] fileprivate var container: _BencodeDecodingContainer? fileprivate let data: Data public var remainingData: Data? { container?.remainingData } init(data: Data, codingPath: [CodingKey] = [], userInfo: [CodingUserInfoKey: Any]) { self.data = data self.codingPath = codingPath self.userInfo = userInfo } } extension _BencodeDecoder: Decoder { func container(keyedBy type: Key.Type) -> KeyedDecodingContainer where Key: CodingKey { let container = KeyedContainer(data: data, codingPath: codingPath, userInfo: userInfo) self.container = container return KeyedDecodingContainer(container) } func unkeyedContainer() -> UnkeyedDecodingContainer { let container = UnkeyedContainer(data: data, codingPath: codingPath, userInfo: userInfo) self.container = container return container } func singleValueContainer() -> SingleValueDecodingContainer { let container = SingleValueContainer(data: data, codingPath: codingPath, userInfo: userInfo) self.container = container return container } } // MARK: - Decoding Logic extension _BencodeDecoder { private struct BencodeString { let value: String? let rawValue: Data } /// Extract the data for the next element (including the `Bencode.Element` info) private static func elementData(_ codingPath: [CodingKey], _ data: Data) throws -> Data { guard let separatorData: Data = "\(Bencode.Element.separator.rawValue)".data(using: .utf8), let endIndicatorData: Data = "\(Bencode.Element.endIndicator.rawValue)".data(using: .utf8) else { throw DecodingError.dataCorrupted(DecodingError.Context( codingPath: codingPath, debugDescription: "could not convert bencode element prefixes to data" )) } var mutableData: Data = data switch Bencode.Element(data.first) { case .number0, .number1, .number2, .number3, .number4, .number5, .number6, .number7, .number8, .number9: var lengthData: [UInt8] = [] // Remove bytes until we hit the separator (separator will be dropped) while let next: UInt8 = mutableData.popFirst(), Bencode.Element(next) != .separator { lengthData.append(next) } guard let lengthString: String = String(data: Data(lengthData), encoding: .ascii), let length: Int = Int(lengthString, radix: 10), mutableData.count >= length else { throw DecodingError.dataCorrupted(DecodingError.Context( codingPath: codingPath, debugDescription: "unable to extract length value" )) } // Need to reset the index of the data (it maintains the index after popping/slicing) // See https://forums.swift.org/t/data-subscript/57195 for more info mutableData = Data(mutableData) mutableData = (lengthData + separatorData + mutableData[0.. (value: BencodeString, remainingData: Data)? { var mutableData: Data = data var lengthData: [UInt8] = [] // Remove bytes until we hit the separator while let next: UInt8 = mutableData.popFirst(), Bencode.Element(next) != .separator { lengthData.append(next) } // Need to reset the index of the data (it maintains the index after popping/slicing) // See https://forums.swift.org/t/data-subscript/57195 for more info mutableData = Data(mutableData) guard let lengthString: String = String(data: Data(lengthData), encoding: .ascii), let length: Int = Int(lengthString, radix: 10), mutableData.count >= length else { return nil } // Need to reset the index of the data (it maintains the index after popping/slicing) // See https://forums.swift.org/t/data-subscript/57195 for more info return ( BencodeString( value: String(data: mutableData[0.. where Key: CodingKey { var codingPath: [CodingKey] var userInfo: [CodingUserInfoKey: Any] var data: Data var remainingData: Data? lazy var nestedContainers: [String: _BencodeDecodingContainer] = { guard Bencode.Element(self.data.first) == .dictIndicator else { return [:] } var mutableData: Data = self.data var nestedContainers: [String: _BencodeDecodingContainer] = [:] _ = mutableData.popFirst() // drop `d` while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Bencode.Element(next) != .endIndicator { guard let keyResult = _BencodeDecoder.decodeString(mutableData), let key: String = keyResult.value.value else { return [:] } mutableData = keyResult.remainingData // drop key data let container: _BencodeDecoder.SingleValueContainer = _BencodeDecoder.SingleValueContainer( data: mutableData, codingPath: self.codingPath, userInfo: self.userInfo ) nestedContainers[key] = container mutableData = (container.remainingData ?? Data()) } return nestedContainers }() init(data: Data, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: Any]) { self.codingPath = codingPath self.userInfo = userInfo let elementData: Data = ((try? _BencodeDecoder.elementData(codingPath, data)) ?? Data()) self.data = elementData self.remainingData = data.dropFirst(elementData.count) } func nestedCodingPath(forKey key: CodingKey) -> [CodingKey] { return codingPath + [key] } func checkCanDecodeValue(forKey key: Key) throws { guard contains(key) else { throw DecodingError.keyNotFound( key, DecodingError.Context(codingPath: codingPath, debugDescription: "key not found: \(key.stringValue)") ) } } } } extension _BencodeDecoder.KeyedContainer: _BencodeDecodingContainer {} extension _BencodeDecoder.KeyedContainer: KeyedDecodingContainerProtocol { var allKeys: [Key] { nestedContainers.keys.compactMap { Key(stringValue: $0) } } func contains(_ key: Key) -> Bool { nestedContainers.keys.contains(key.stringValue) } func decodeNil(forKey key: Key) throws -> Bool { throw DecodingError.typeMismatch( Any?.self, DecodingError.Context( codingPath: codingPath, debugDescription: "cannot decode nil for key: \(key) (Null values are not supported)" ) ) } func decode(_ type: T.Type, forKey key: Key) throws -> T where T: Decodable { try checkCanDecodeValue(forKey: key) guard let container: _BencodeDecodingContainer = nestedContainers[key.stringValue] else { throw DecodingError.keyNotFound( key, DecodingError.Context(codingPath: codingPath, debugDescription: "key not found: \(key.stringValue)") ) } let decoder: BencodeDecoder = BencodeDecoder(userInfo: userInfo) return try decoder.decode(T.self, from: container.data) } func nestedUnkeyedContainer(forKey key: Key) throws -> UnkeyedDecodingContainer { try checkCanDecodeValue(forKey: key) guard let unkeyedContainer = nestedContainers[key.stringValue] as? _BencodeDecoder.UnkeyedContainer else { throw DecodingError.dataCorruptedError( forKey: key, in: self, debugDescription: "cannot decode nested container for key: \(key)" ) } return unkeyedContainer } func nestedContainer(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer where NestedKey: CodingKey { try checkCanDecodeValue(forKey: key) guard let keyedContainer = nestedContainers[key.stringValue] as? _BencodeDecoder.KeyedContainer else { throw DecodingError.dataCorruptedError( forKey: key, in: self, debugDescription: "cannot decode nested container for key: \(key)" ) } return KeyedDecodingContainer(keyedContainer) } func superDecoder() throws -> Decoder { return _BencodeDecoder(data: data, userInfo: userInfo) } func superDecoder(forKey key: Key) throws -> Decoder { let decoder = _BencodeDecoder(data: data, userInfo: userInfo) decoder.codingPath = [key] return decoder } } // MARK: - UnkeyedContainer extension _BencodeDecoder { final class UnkeyedContainer { var codingPath: [CodingKey] var userInfo: [CodingUserInfoKey: Any] var nestedCodingPath: [CodingKey] { codingPath + [Bencode.UnkeyedCodingKey(intValue: count ?? 0)] } var data: Data var remainingData: Data? lazy var nestedContainers: [_BencodeDecodingContainer] = { /// If the first element isn't a `listIndicator` then the content is likely a single element which is represented under the hood /// by an array (eg. `String` or `Data`), or is a single Bencoded element followed by additional bytes guard Bencode.Element(data.first) == .listIndicator else { return [ _BencodeDecoder.SingleValueContainer( data: data, codingPath: codingPath, userInfo: userInfo ) ] } var mutableData: Data = data var nestedContainers: [_BencodeDecodingContainer] = [] _ = mutableData.popFirst() // drop `l` while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Bencode.Element(next) != .endIndicator { let container: _BencodeDecoder.SingleValueContainer = _BencodeDecoder.SingleValueContainer( data: mutableData, codingPath: codingPath, userInfo: userInfo ) nestedContainers.append(container) mutableData = (container.remainingData ?? Data()) } return nestedContainers }() lazy var count: Int? = { nestedContainers.count }() var isAtEnd: Bool { currentIndex >= (count ?? 0) } var currentIndex: Int = 0 init(data: Data, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: Any]) { self.codingPath = codingPath self.userInfo = userInfo let elementData: Data = ((try? _BencodeDecoder.elementData(codingPath, data)) ?? Data()) self.data = elementData self.remainingData = data.dropFirst(elementData.count) } func checkCanDecodeValue() throws { guard !isAtEnd else { throw DecodingError.dataCorruptedError(in: self, debugDescription: "Unexpected end of data") } } } } extension _BencodeDecoder.UnkeyedContainer: _BencodeDecodingContainer {} extension _BencodeDecoder.UnkeyedContainer: UnkeyedDecodingContainer { func decodeNil() throws -> Bool { throw DecodingError.typeMismatch( Any?.self, DecodingError.Context( codingPath: codingPath, debugDescription: "cannot decode nil for index: \(currentIndex) (Null values are not supported)" ) ) } func decode(_ type: T.Type) throws -> T where T: Decodable { try checkCanDecodeValue() defer { self.currentIndex += 1 } let container: _BencodeDecodingContainer = self.nestedContainers[self.currentIndex] let error: DecodingError = DecodingError.dataCorrupted( DecodingError.Context(codingPath: codingPath, debugDescription: "unable to decode \(type)") ) switch type { case is Data.Type: // Custom handle data as iOS sees it as an array let result = try _BencodeDecoder.decodeString(container.data) ?? { throw error }() return try result.value.rawValue as? T ?? { throw error }() default: let decoder: BencodeDecoder = BencodeDecoder(userInfo: userInfo) return try decoder.decode(T.self, from: container.data) } } func nestedUnkeyedContainer() throws -> UnkeyedDecodingContainer { try checkCanDecodeValue() defer { self.currentIndex += 1 } guard let container: _BencodeDecoder.UnkeyedContainer = nestedContainers[currentIndex] as? _BencodeDecoder.UnkeyedContainer else { throw DecodingError.typeMismatch( _BencodeDecoder.UnkeyedContainer.self, DecodingError.Context( codingPath: codingPath, debugDescription: "nested container is not an UnkeyedContainer" ) ) } return container } func nestedContainer(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer where NestedKey: CodingKey { try checkCanDecodeValue() defer { self.currentIndex += 1 } guard let container: _BencodeDecoder.KeyedContainer = nestedContainers[currentIndex] as? _BencodeDecoder.KeyedContainer else { throw DecodingError.typeMismatch( _BencodeDecoder.UnkeyedContainer.self, DecodingError.Context( codingPath: codingPath, debugDescription: "nested container is not an KeyedContainer" ) ) } return KeyedDecodingContainer(container) } func superDecoder() throws -> Decoder { return _BencodeDecoder(data: data, userInfo: userInfo) } } // MARK: - SingleValueContainer extension _BencodeDecoder { final class SingleValueContainer { var codingPath: [CodingKey] var userInfo: [CodingUserInfoKey: Any] var data: Data var remainingData: Data? init(data: Data, codingPath: [CodingKey], userInfo: [CodingUserInfoKey: Any]) { self.codingPath = codingPath self.userInfo = userInfo let elementData: Data? = (try? _BencodeDecoder.elementData(codingPath, data)) self.data = (elementData ?? data) self.remainingData = elementData.map { data.dropFirst($0.count) } } } } extension _BencodeDecoder.SingleValueContainer: _BencodeDecodingContainer {} extension _BencodeDecoder.SingleValueContainer: SingleValueDecodingContainer { func decodeNil() -> Bool { return true } // Nil values are omitted in Bencoded data func decode(_ type: Bool.Type) throws -> Bool { throw DecodingError.typeMismatch( Bool.self, DecodingError.Context( codingPath: codingPath, debugDescription: "Bencode doesn't support Bool values, use an Int and custom Encode/Decode functions isntead" ) ) } func decode(_ type: String.Type) throws -> String { guard let decodedData = _BencodeDecoder.decodeString(data), let result: String = decodedData.value.value else { throw DecodingError.dataCorrupted( DecodingError.Context( codingPath: codingPath, debugDescription: "failed to decode String" ) ) } return result } func decode(_ type: Double.Type) throws -> Double { let intValue: Int = try decodeFixedInt(Int.self) return Double(intValue) } func decode(_ type: Float.Type) throws -> Float { let intValue: Int = try decodeFixedInt(Int.self) return Float(intValue) } func decode(_ type: Int.Type) throws -> Int { return try decodeFixedInt(type) } func decode(_ type: Int8.Type) throws -> Int8 { return try decodeFixedInt(type) } func decode(_ type: Int16.Type) throws -> Int16 { return try decodeFixedInt(type) } func decode(_ type: Int32.Type) throws -> Int32 { return try decodeFixedInt(type) } func decode(_ type: Int64.Type) throws -> Int64 { return try decodeFixedInt(type) } func decode(_ type: UInt.Type) throws -> UInt { return try decodeFixedInt(type) } func decode(_ type: UInt8.Type) throws -> UInt8 { return try decodeFixedInt(type) } func decode(_ type: UInt16.Type) throws -> UInt16 { return try decodeFixedInt(type) } func decode(_ type: UInt32.Type) throws -> UInt32 { return try decodeFixedInt(type) } func decode(_ type: UInt64.Type) throws -> UInt64 { return try decodeFixedInt(type) } func decodeFixedInt(_ type: T.Type) throws -> T { var mutableData: Data = data var intData: [UInt8] = [] _ = mutableData.popFirst() // drop `i` // Pop until after `e` while let next: UInt8 = mutableData.popFirst(), Bencode.Element(next) != .endIndicator { intData.append(next) } guard let intString: String = String(data: Data(intData), encoding: .ascii), let result: T = T(intString, radix: 10) else { throw DecodingError.dataCorrupted( DecodingError.Context( codingPath: codingPath, debugDescription: "failed to decode Int" ) ) } return result } func decode(_ type: T.Type) throws -> T where T: Decodable { if T.self is any FixedWidthInteger.Type { // This will be handled by the integer-specific decode function throw DecodingError.typeMismatch( T.self, DecodingError.Context( codingPath: codingPath, debugDescription: "attempted to use generic decode function instead of integer-specific one for integer type" ) ) } let decoder = _BencodeDecoder(data: data, userInfo: userInfo) let value = try T(from: decoder) return value } }