You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionUtilitiesKit/Utilities/BencodeDecoder.swift

621 lines
25 KiB
Swift

// 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<T>(_ 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<T>(_ 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<Key>(keyedBy type: Key.Type) -> KeyedDecodingContainer<Key> where Key: CodingKey {
let container = KeyedContainer<Key>(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..<length]) // overwrite with content
case .intIndicator:
var intData: [UInt8] = (mutableData.popFirst().map { [$0] } ?? []) // drop `i`
// Remove bytes until we hit the end (endIndicator will be dropped)
while let next: UInt8 = mutableData.popFirst(), Bencode.Element(next) != .endIndicator {
intData.append(next)
}
intData.append(contentsOf: endIndicatorData) // append `e`
mutableData = Data(intData) // overwrite with content
case .listIndicator:
var listData: [UInt8] = (mutableData.popFirst().map { [$0] } ?? []) // drop `l`
// Extract the elements
while let next: UInt8 = mutableData.first, Bencode.Element(next) != .endIndicator {
let elementData: Data = try elementData(codingPath, mutableData)
listData.append(contentsOf: elementData) // append the element
mutableData = mutableData.dropFirst(elementData.count) // drop the element
}
listData.append(contentsOf: mutableData.popFirst().map { [$0] } ?? []) // drop `e`
mutableData = Data(listData) // overwrite with content
case .dictIndicator:
var dictData: [UInt8] = (mutableData.popFirst().map { [$0] } ?? []) // drop `d`
// Extract the elements
while let next: UInt8 = mutableData.first, Bencode.Element(next) != .endIndicator {
let keyData: Data = try elementData(codingPath, mutableData)
dictData.append(contentsOf: keyData) // append the key
mutableData = mutableData.dropFirst(keyData.count) // drop the key
let valueData: Data = try elementData(codingPath, mutableData)
dictData.append(contentsOf: valueData) // append the value
mutableData = mutableData.dropFirst(valueData.count) // drop the value
}
dictData.append(contentsOf: mutableData.popFirst().map { [$0] } ?? []) // drop `e`
mutableData = Data(dictData) // overwrite with content
default:
let actualValue: String = (data.first.map { String(data: Data([$0]), encoding: .ascii) } ?? "null")
throw DecodingError.typeMismatch(
Bencode.Element.self,
DecodingError.Context(
codingPath: codingPath,
debugDescription: "invalid element prefix: \(actualValue)"
)
)
}
return Data(mutableData)
}
/// Decode a string element from iterator assumed to have structure `{length}:{data}`
private static func decodeString(_ data: Data) -> (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..<length], encoding: .ascii),
rawValue: mutableData[0..<length]
),
Data(mutableData.dropFirst(length))
)
}
}
// MARK: - KeyedContainer
extension _BencodeDecoder {
final class KeyedContainer<Key> 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<T>(_ 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<NestedKey>(keyedBy type: NestedKey.Type, forKey key: Key) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
try checkCanDecodeValue(forKey: key)
guard let keyedContainer = nestedContainers[key.stringValue] as? _BencodeDecoder.KeyedContainer<NestedKey> 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<T>(_ 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<NestedKey>(keyedBy type: NestedKey.Type) throws -> KeyedDecodingContainer<NestedKey> where NestedKey: CodingKey {
try checkCanDecodeValue()
defer { self.currentIndex += 1 }
guard let container: _BencodeDecoder.KeyedContainer<NestedKey> = nestedContainers[currentIndex] as? _BencodeDecoder.KeyedContainer<NestedKey> 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<T: FixedWidthInteger>(_ 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<T>(_ 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
}
}