mirror of https://github.com/oxen-io/session-ios
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.
264 lines
9.7 KiB
Swift
264 lines
9.7 KiB
Swift
2 years ago
|
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
||
|
|
||
|
import Foundation
|
||
|
|
||
|
public protocol BencodableType {
|
||
|
associatedtype ValueType: BencodableType
|
||
|
|
||
|
static var isCollection: Bool { get }
|
||
|
static var isDictionary: Bool { get }
|
||
|
}
|
||
|
|
||
|
public struct BencodeResponse<T: Codable> {
|
||
|
public let info: T
|
||
|
public let data: Data?
|
||
|
}
|
||
|
|
||
|
extension BencodeResponse: Equatable where T: Equatable {}
|
||
|
|
||
|
public enum Bencode {
|
||
|
private enum Element: Character {
|
||
|
case number0 = "0"
|
||
|
case number1 = "1"
|
||
|
case number2 = "2"
|
||
|
case number3 = "3"
|
||
|
case number4 = "4"
|
||
|
case number5 = "5"
|
||
|
case number6 = "6"
|
||
|
case number7 = "7"
|
||
|
case number8 = "8"
|
||
|
case number9 = "9"
|
||
|
case intIndicator = "i"
|
||
|
case listIndicator = "l"
|
||
|
case dictIndicator = "d"
|
||
|
case endIndicator = "e"
|
||
|
case separator = ":"
|
||
|
|
||
|
init?(_ byte: UInt8?) {
|
||
|
guard
|
||
|
let byte: UInt8 = byte,
|
||
|
let byteString: String = String(data: Data([byte]), encoding: .utf8),
|
||
|
let character: Character = byteString.first,
|
||
|
let result: Element = Element(rawValue: character)
|
||
|
else { return nil }
|
||
|
|
||
|
self = result
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private struct BencodeString {
|
||
|
let value: String?
|
||
|
let rawValue: Data
|
||
|
}
|
||
|
|
||
|
// MARK: - Functions
|
||
|
|
||
|
public static func decodeResponse<T>(
|
||
|
from data: Data,
|
||
|
using dependencies: Dependencies = Dependencies()
|
||
|
) throws -> BencodeResponse<T> where T: Decodable {
|
||
|
guard
|
||
|
let result: [Data] = try? decode([Data].self, from: data),
|
||
|
let responseData: Data = result.first
|
||
|
else { throw HTTPError.parsingFailed }
|
||
|
|
||
|
return BencodeResponse(
|
||
|
info: try responseData.decoded(as: T.self, using: dependencies),
|
||
|
data: (result.count > 1 ? result.last : nil)
|
||
|
)
|
||
|
}
|
||
|
|
||
|
public static func decode<T: BencodableType>(_ type: T.Type, from data: Data) throws -> T {
|
||
|
guard
|
||
|
let decodedData: (value: Any, remainingData: Data) = decodeData(data),
|
||
|
decodedData.remainingData.isEmpty == true // Ensure there is no left over data
|
||
|
else { throw HTTPError.parsingFailed }
|
||
|
|
||
|
return try recursiveCast(type, from: decodedData.value)
|
||
|
}
|
||
|
|
||
|
// MARK: - Logic
|
||
|
|
||
|
private static func decodeData(_ data: Data) -> (value: Any, remainingData: Data)? {
|
||
|
switch Element(data.first) {
|
||
|
case .number0, .number1, .number2, .number3, .number4,
|
||
|
.number5, .number6, .number7, .number8, .number9:
|
||
|
return decodeString(data)
|
||
|
|
||
|
case .intIndicator: return decodeInt(data)
|
||
|
case .listIndicator: return decodeList(data)
|
||
|
case .dictIndicator: return decodeDict(data)
|
||
|
default: return nil
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// 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(), 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))
|
||
|
)
|
||
|
}
|
||
|
|
||
|
/// Decode an int element from iterator assumed to have structure `i{int}e`
|
||
|
private static func decodeInt(_ data: Data) -> (value: Int, remainingData: Data)? {
|
||
|
var mutableData: Data = data
|
||
|
var intData: [UInt8] = []
|
||
|
_ = mutableData.popFirst() // drop `i`
|
||
|
|
||
|
// Pop until after `e`
|
||
|
while let next: UInt8 = mutableData.popFirst(), Element(next) != .endIndicator {
|
||
|
intData.append(next)
|
||
|
}
|
||
|
|
||
|
guard
|
||
|
let intString: String = String(data: Data(intData), encoding: .ascii),
|
||
|
let result: Int = Int(intString, radix: 10)
|
||
|
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 (result, Data(mutableData))
|
||
|
}
|
||
|
|
||
|
/// Decode a list element from iterator assumed to have structure `l{data}e`
|
||
|
private static func decodeList(_ data: Data) -> ([Any], Data)? {
|
||
|
var mutableData: Data = data
|
||
|
var listElements: [Any] = []
|
||
|
_ = mutableData.popFirst() // drop `l`
|
||
|
|
||
|
while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Element(next) != .endIndicator {
|
||
|
guard let result = decodeData(mutableData) else { break }
|
||
|
|
||
|
listElements.append(result.value)
|
||
|
mutableData = result.remainingData
|
||
|
}
|
||
|
|
||
|
_ = mutableData.popFirst() // drop `e`
|
||
|
|
||
|
// 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 (listElements, Data(mutableData))
|
||
|
}
|
||
|
|
||
|
/// Decode a dict element from iterator assumed to have structure `d{data}e`
|
||
|
private static func decodeDict(_ data: Data) -> ([String: Any], Data)? {
|
||
|
var mutableData: Data = data
|
||
|
var dictElements: [String: Any] = [:]
|
||
|
_ = mutableData.popFirst() // drop `d`
|
||
|
|
||
|
while !mutableData.isEmpty, let next: UInt8 = mutableData.first, Element(next) != .endIndicator {
|
||
|
guard
|
||
|
let keyResult = decodeString(mutableData),
|
||
|
let key: String = keyResult.value.value,
|
||
|
let valueResult = decodeData(keyResult.remainingData)
|
||
|
else { return nil }
|
||
|
|
||
|
dictElements[key] = valueResult.value
|
||
|
mutableData = valueResult.remainingData
|
||
|
}
|
||
|
|
||
|
_ = mutableData.popFirst() // drop `e`
|
||
|
|
||
|
// 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 (dictElements, Data(mutableData))
|
||
|
}
|
||
|
|
||
|
// MARK: - Internal Functions
|
||
|
|
||
|
private static func recursiveCast<T: BencodableType>(_ type: T.Type, from value: Any) throws -> T {
|
||
|
switch (type.isCollection, type.isDictionary) {
|
||
|
case (_, true):
|
||
|
guard let dictValue: [String: Any] = value as? [String: Any] else { throw HTTPError.parsingFailed }
|
||
|
|
||
|
return try (
|
||
|
dictValue.mapValues { try recursiveCast(type.ValueType.self, from: $0) } as? T ??
|
||
|
{ throw HTTPError.parsingFailed }()
|
||
|
)
|
||
|
|
||
|
case (true, _):
|
||
|
guard let arrayValue: [Any] = value as? [Any] else { throw HTTPError.parsingFailed }
|
||
|
|
||
|
return try (
|
||
|
arrayValue.map { try recursiveCast(type.ValueType.self, from: $0) } as? T ??
|
||
|
{ throw HTTPError.parsingFailed }()
|
||
|
)
|
||
|
|
||
|
default:
|
||
|
switch (value, type) {
|
||
|
case (let bencodeString as BencodeString, is String.Type):
|
||
|
return try (bencodeString.value as? T ?? { throw HTTPError.parsingFailed }())
|
||
|
|
||
|
case (let bencodeString as BencodeString, is Optional<String>.Type):
|
||
|
return try (bencodeString.value as? T ?? { throw HTTPError.parsingFailed }())
|
||
|
|
||
|
case (let bencodeString as BencodeString, _):
|
||
|
return try (bencodeString.rawValue as? T ?? { throw HTTPError.parsingFailed }())
|
||
|
|
||
|
default: return try (value as? T ?? { throw HTTPError.parsingFailed }())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// MARK: - BencodableType Extensions
|
||
|
|
||
|
extension Data: BencodableType {
|
||
|
public typealias ValueType = Data
|
||
|
|
||
|
public static var isCollection: Bool { false }
|
||
|
public static var isDictionary: Bool { false }
|
||
|
}
|
||
|
|
||
|
extension Int: BencodableType {
|
||
|
public typealias ValueType = Int
|
||
|
|
||
|
public static var isCollection: Bool { false }
|
||
|
public static var isDictionary: Bool { false }
|
||
|
}
|
||
|
|
||
|
extension String: BencodableType {
|
||
|
public typealias ValueType = String
|
||
|
|
||
|
public static var isCollection: Bool { false }
|
||
|
public static var isDictionary: Bool { false }
|
||
|
}
|
||
|
|
||
|
extension Array: BencodableType where Element: BencodableType {
|
||
|
public typealias ValueType = Element
|
||
|
|
||
|
public static var isCollection: Bool { true }
|
||
|
public static var isDictionary: Bool { false }
|
||
|
}
|
||
|
|
||
|
extension Dictionary: BencodableType where Key == String, Value: BencodableType {
|
||
|
public typealias ValueType = Value
|
||
|
|
||
|
public static var isCollection: Bool { false }
|
||
|
public static var isDictionary: Bool { true }
|
||
|
}
|