diff --git a/Bencode.podspec b/Bencode.podspec index dbdf05a..40fb146 100644 --- a/Bencode.podspec +++ b/Bencode.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Bencode' - s.version = '1.4.0' + s.version = '1.5.0' s.summary = 'Pure swift Bencode decoder & encoder' s.description = <<-DESC diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index c961b80..557d60f 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 0A9FD13A40CAED6AC802DD45 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDC268113B70CCC0CCBC4E0E /* Pods_Example.framework */; }; 22DFB884CC578E0D4300CEF5 /* Pods_ExampleTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7B96CF035A89E1076242960E /* Pods_ExampleTests.framework */; }; + E21448192097105100EE2021 /* 2bytes.torrent in Resources */ = {isa = PBXBuildFile; fileRef = E21448182097105100EE2021 /* 2bytes.torrent */; }; E2AE3B241F6A4DCD002D41D5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AE3B231F6A4DCD002D41D5 /* AppDelegate.swift */; }; E2AE3B261F6A4DCD002D41D5 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AE3B251F6A4DCD002D41D5 /* ViewController.swift */; }; E2AE3B281F6A4DCD002D41D5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E2AE3B271F6A4DCD002D41D5 /* Assets.xcassets */; }; @@ -35,6 +36,7 @@ 84442661E823A9F8505CA65E /* Pods-ExampleTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.release.xcconfig"; sourceTree = ""; }; CDA52018ADD396F648B3D727 /* Pods-ExampleTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ExampleTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-ExampleTests/Pods-ExampleTests.debug.xcconfig"; sourceTree = ""; }; DDC268113B70CCC0CCBC4E0E /* Pods_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + E21448182097105100EE2021 /* 2bytes.torrent */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = 2bytes.torrent; sourceTree = ""; }; E2AE3B201F6A4DCD002D41D5 /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; E2AE3B231F6A4DCD002D41D5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; E2AE3B251F6A4DCD002D41D5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -88,6 +90,15 @@ name = Frameworks; sourceTree = ""; }; + E214481720970FDE00EE2021 /* Resources */ = { + isa = PBXGroup; + children = ( + E2AE3B411F6A4FF0002D41D5 /* Entourage.S01.torrent */, + E21448182097105100EE2021 /* 2bytes.torrent */, + ); + path = Resources; + sourceTree = ""; + }; E2AE3B171F6A4DCD002D41D5 = { isa = PBXGroup; children = ( @@ -111,12 +122,12 @@ E2AE3B221F6A4DCD002D41D5 /* Example */ = { isa = PBXGroup; children = ( - E2AE3B411F6A4FF0002D41D5 /* Entourage.S01.torrent */, + E2AE3B2C1F6A4DCD002D41D5 /* Info.plist */, E2AE3B231F6A4DCD002D41D5 /* AppDelegate.swift */, E2AE3B251F6A4DCD002D41D5 /* ViewController.swift */, E2AE3B271F6A4DCD002D41D5 /* Assets.xcassets */, E2AE3B291F6A4DCD002D41D5 /* Main.storyboard */, - E2AE3B2C1F6A4DCD002D41D5 /* Info.plist */, + E214481720970FDE00EE2021 /* Resources */, E2AE3B2D1F6A4DCD002D41D5 /* Example.entitlements */, ); path = Example; @@ -220,6 +231,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + E21448192097105100EE2021 /* 2bytes.torrent in Resources */, E2AE3B421F6A5762002D41D5 /* Entourage.S01.torrent in Resources */, E2AE3B281F6A4DCD002D41D5 /* Assets.xcassets in Resources */, E2AE3B2B1F6A4DCD002D41D5 /* Main.storyboard in Resources */, diff --git a/Example/Example/Resources/2bytes.torrent b/Example/Example/Resources/2bytes.torrent new file mode 100644 index 0000000..5624b64 Binary files /dev/null and b/Example/Example/Resources/2bytes.torrent differ diff --git a/Example/Example/Entourage.S01.torrent b/Example/Example/Resources/Entourage.S01.torrent similarity index 100% rename from Example/Example/Entourage.S01.torrent rename to Example/Example/Resources/Entourage.S01.torrent diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 00717f5..7858d2c 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -25,7 +25,7 @@ final class ViewController: NSViewController { super.viewDidLoad() // Do any additional setup after loading the view. - guard let url = Bundle.main.url(forResource: "Entourage.S01", withExtension: "torrent"), + guard let url = Bundle.main.url(forResource: "2bytes", withExtension: "torrent"), let bencode = Bencode(file: url) else { return } @@ -43,11 +43,6 @@ final class ViewController: NSViewController { print("\n==========================================\n") - let encodedInfo = info.encoded - print(encodedInfo) - - print("\n==========================================\n") - guard let data = try? Data(contentsOf: url) else { return } print(data == bencode.asciiEncoding) diff --git a/README.md b/README.md index c99fca5..8d9c0a8 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,16 @@ let bencode: Bencode? = Bencode(file: torrentUrl) let bencode: Bencode? = Bencode(bencodedString: content) ``` +**Would you like to know more of the parsing failures, use the decoder:** + +```swift +do { + let bencode = Bencoder().decode(from: fileURL) +} catch let error { + print(error) +} +``` + **Accessing properties:** Accessing properties is very handy with subscripts & accessors. diff --git a/Source/Bencode.swift b/Source/Bencode.swift index 1bfd837..e81e35a 100644 --- a/Source/Bencode.swift +++ b/Source/Bencode.swift @@ -20,131 +20,32 @@ public extension Bencode { /** Decoding from Bencoded string */ init?(bencodedString str: String) { - self.init(bytes: str.ascii) + guard let bencode = try? Bencoder().decode(bencodedString: str) + else { return nil } + self = bencode } /** Decoding bencoded file */ init?(file url: URL) { - guard let data = try? Data(contentsOf: url) + guard let bencode = try? Bencoder().decode(file: url) else { return nil } - self.init(bytes: [UInt8](data)) + self = bencode } /** Decoding from bytes */ init?(bytes: [UInt8]) { - guard let bencode = Bencode.parse(bytes)?.bencode + guard let bencode = try? Bencoder().decode(bytes: bytes) else { return nil } self = bencode } /** Encoding to Bencode string */ var encoded: String { - switch self { - case .integer(let i): return "i\(i)e" - case .string(let b): return "\(b.count):\(String(bytes: b, encoding: .ascii)!)" - case .list(let l): - let desc = l.map { $0.encoded }.joined() - return "l\(desc)e" - case .dictionary(let d): - let desc = d.sorted(by: { $0.key < $1.key }) - .map { "\(Bencode.string($0.key.ascii).encoded)\($1.encoded)" } - .joined() - return "d\(desc)e" - } + return Bencoder().encoded(bencode: self) } /** Encoding to Bencoded Data */ var asciiEncoding: Data { - return Data(bytes: encoded.ascii) - } -} - -// MARK: - Private decding helpers - -private extension Bencode { - - private struct Tokens { - static let i: UInt8 = 0x69 - static let l: UInt8 = 0x6c - static let d: UInt8 = 0x64 - static let e: UInt8 = 0x65 - static let zero: UInt8 = 0x30 - static let nine: UInt8 = 0x39 - static let colon: UInt8 = 0x3a - static let hyphen: UInt8 = 0x2d - } - - typealias ParseResult = (bencode: Bencode, index: Int) - - static func parse(_ data: [UInt8]) -> ParseResult? { - return parse(ArraySlice(data), from: 0) - } - - static private func parse(_ data: ArraySlice, from index: Int) -> ParseResult? { - - guard data.count > index + 1 - else { return nil } - - let nextIndex = index+1 - let nextSlice = data[nextIndex...] - - switch data[index] { - case Tokens.i: return parseInt(nextSlice, from: nextIndex) - case Tokens.zero...Tokens.nine: return parseString(data, from: index) - case Tokens.l: return parseList(nextSlice, from: nextIndex) - case Tokens.d: return parseDictionary(nextSlice, from: nextIndex) - default: return nil - } - } - - static func parseInt(_ data: ArraySlice, from index: Int) -> ParseResult? { - guard let end = data.index(of: Tokens.e), - let num = Array(data[.., from index: Int) -> ParseResult? { - guard let sep = data.index(of: Tokens.colon), - let len = Array(data[.., from index: Int) -> ParseResult? { - var l: [Bencode] = [] - var idx: Int = index - - while data[idx] != Tokens.e { - guard let result = parse(data[idx...], from: idx) - else { return nil } - l.append(result.bencode) - idx = result.index - } - - return (bencode: .list(l), index: idx+1) - } - - static func parseDictionary(_ data: ArraySlice, from index: Int) -> ParseResult? { - var d: [BencodeKey:Bencode] = [:] - var idx: Int = index - var order = 0 - - while data[idx] != Tokens.e { - guard let keyResult = parseString(data[idx...], from: idx), - case .string(let keyData) = keyResult.bencode, - let key = keyData.string, - let valueResult = parse(data[keyResult.index...], from: keyResult.index) - else { return nil } - - d[BencodeKey(key, order: order)] = valueResult.bencode - idx = valueResult.index - order += 1 - } - return (bencode: .dictionary(d), index: idx+1) + return Bencoder().asciiEncoding(bencode: self) } } diff --git a/Source/Bencoder.swift b/Source/Bencoder.swift new file mode 100644 index 0000000..eadf313 --- /dev/null +++ b/Source/Bencoder.swift @@ -0,0 +1,139 @@ +// +// Bencoder.swift +// Bencode +// +// Created by Daniel Tombor on 2018. 04. 30.. +// + +import Foundation + +final class Bencoder { + + // MARK: - Constants + + private struct Tokens { + let i: UInt8 = 0x69 + let l: UInt8 = 0x6c + let d: UInt8 = 0x64 + let e: UInt8 = 0x65 + let zero: UInt8 = 0x30 + let nine: UInt8 = 0x39 + let colon: UInt8 = 0x3a + } + + private let tokens = Tokens() + + // MARK: - Methods + + /** Decoding from Bencoded string */ + func decode(bencodedString str: String) throws -> Bencode { + return try decode(bytes: str.ascii) + } + + /** Decoding bencoded file */ + func decode(file url: URL) throws -> Bencode { + let data = try Data(contentsOf: url) + return try decode(bytes: [Byte](data)) + } + + /** Decoding from bytes */ + func decode(bytes: [UInt8]) throws -> Bencode { + return try parse(bytes).bencode + } + + /** Encoding to Bencode string */ + func encoded(bencode: Bencode) -> String { + switch bencode { + case .integer(let i): return "i\(i)e" + case .string(let b): return "\(b.count):\(String(bytes: b, encoding: .ascii)!)" + case .list(let l): + let desc = l.map { $0.encoded }.joined() + return "l\(desc)e" + case .dictionary(let d): + let desc = d.sorted(by: { $0.key < $1.key }) + .map { "\(Bencode.string($0.key.ascii).encoded)\($1.encoded)" } + .joined() + return "d\(desc)e" + } + } + + /** Encoding to Bencoded Data */ + func asciiEncoding(bencode: Bencode) -> Data { + return Data(bytes: encoded(bencode: bencode).ascii) + } +} + +// MARK: - Private decoding helpers + +private extension Bencoder { + + typealias ParseResult = (bencode: Bencode, index: Int) + + func parse(_ data: [Byte]) throws -> ParseResult { + return try parse(ArraySlice(data), from: 0) + } + + func parse(_ data: ArraySlice, from index: Int) throws -> ParseResult { + guard data.endIndex >= index + 1 + else { throw BencoderError.indexOutOfBounds(end: data.endIndex, current: index + 1) } + + let nextIndex = index+1 + let nextSlice = data[nextIndex...] + + switch data[index] { + case tokens.i: return try parseInt(nextSlice, from: nextIndex) + case tokens.zero...tokens.nine: return try parseString(data, from: index) + case tokens.l: return try parseList(nextSlice, from: nextIndex) + case tokens.d: return try parseDictionary(nextSlice, from: nextIndex) + default: throw BencoderError.unknownToken(data[index]) + } + } + + func parseInt(_ data: ArraySlice, from index: Int) throws -> ParseResult { + guard let end = data.index(of: tokens.e) + else { throw BencoderError.tokenNotFound(tokens.e) } + guard let num = Array(data[.., from index: Int) throws -> ParseResult { + guard let sep = data.index(of: tokens.colon) + else { throw BencoderError.tokenNotFound(tokens.colon) } + guard let len = Array(data[.., from index: Int) throws -> ParseResult { + var l: [Bencode] = [] + var idx: Int = index + + while data[idx] != tokens.e { + let result = try parse(data[idx...], from: idx) + l.append(result.bencode) + idx = result.index + } + return (bencode: .list(l), index: idx+1) + } + + func parseDictionary(_ data: ArraySlice, from index: Int) throws -> ParseResult { + var d: [BencodeKey:Bencode] = [:] + var idx: Int = index + var order = 0 + + while data[idx] != tokens.e { + let keyResult = try parseString(data[idx...], from: idx) + guard case .string(let keyData) = keyResult.bencode, + let key = keyData.string + else { throw BencoderError.unexpectedKey(keyResult.bencode) } + let valueResult = try parse(data[keyResult.index...], from: keyResult.index) + d[BencodeKey(key, order: order)] = valueResult.bencode + idx = valueResult.index + order += 1 + } + return (bencode: .dictionary(d), index: idx+1) + } +} diff --git a/Source/BencoderError.swift b/Source/BencoderError.swift new file mode 100644 index 0000000..89b1170 --- /dev/null +++ b/Source/BencoderError.swift @@ -0,0 +1,16 @@ +// +// BencoderError.swift +// Bencode +// +// Created by Daniel Tombor on 2018. 04. 30.. +// + +import Foundation + +enum BencoderError: Error { + case unknownToken(UInt8) + case tokenNotFound(UInt8) + case unexpectedKey(Bencode) + case invalidNumber + case indexOutOfBounds(end: Int, current: Int) +} diff --git a/Source/Extensions.swift b/Source/Extensions.swift index d29a49a..f882eec 100644 --- a/Source/Extensions.swift +++ b/Source/Extensions.swift @@ -9,7 +9,7 @@ import Foundation // MARK: - [UInt8] Extension -internal typealias Byte = (UInt8) +internal typealias Byte = UInt8 internal extension Sequence where Iterator.Element == Byte { @@ -28,7 +28,7 @@ internal extension Sequence where Iterator.Element == Byte { internal extension String { - var ascii: [UInt8] { - return unicodeScalars.map { return UInt8($0.value) } + var ascii: [Byte] { + return unicodeScalars.map { return Byte($0.value) } } }