Skip to content

Commit

Permalink
- fix parsing bug
Browse files Browse the repository at this point in the history
- separate parsing into Bencoder class
- throw custom errors at parsing
  • Loading branch information
danieltmbr committed Apr 30, 2018
1 parent 4112004 commit fa54fd9
Show file tree
Hide file tree
Showing 10 changed files with 192 additions and 119 deletions.
2 changes: 1 addition & 1 deletion Bencode.podspec
Original file line number Diff line number Diff line change
@@ -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
Expand Down
16 changes: 14 additions & 2 deletions Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand All @@ -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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
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 = "<group>"; };
E2AE3B251F6A4DCD002D41D5 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -88,6 +90,15 @@
name = Frameworks;
sourceTree = "<group>";
};
E214481720970FDE00EE2021 /* Resources */ = {
isa = PBXGroup;
children = (
E2AE3B411F6A4FF0002D41D5 /* Entourage.S01.torrent */,
E21448182097105100EE2021 /* 2bytes.torrent */,
);
path = Resources;
sourceTree = "<group>";
};
E2AE3B171F6A4DCD002D41D5 = {
isa = PBXGroup;
children = (
Expand All @@ -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;
Expand Down Expand Up @@ -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 */,
Expand Down
Binary file added Example/Example/Resources/2bytes.torrent
Binary file not shown.
File renamed without changes.
7 changes: 1 addition & 6 deletions Example/Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }

Expand All @@ -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)
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
115 changes: 8 additions & 107 deletions Source/Bencode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<UInt8>, 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<UInt8>, from index: Int) -> ParseResult? {
guard let end = data.index(of: Tokens.e),
let num = Array(data[..<end]).int
else { return nil }
return (bencode: .integer(num), index: end+1)
}

static func parseString(_ data: ArraySlice<UInt8>, from index: Int) -> ParseResult? {
guard let sep = data.index(of: Tokens.colon),
let len = Array(data[..<sep]).int
else { return nil }

let start = sep + 1
let end = data.index(start, offsetBy: len)

return (bencode: .string(Array(data[start..<end])), index: end)
}

static func parseList(_ data: ArraySlice<UInt8>, 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<UInt8>, 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)
}
}
139 changes: 139 additions & 0 deletions Source/Bencoder.swift
Original file line number Diff line number Diff line change
@@ -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<Byte>, 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<Byte>, from index: Int) throws -> ParseResult {
guard let end = data.index(of: tokens.e)
else { throw BencoderError.tokenNotFound(tokens.e) }
guard let num = Array(data[..<end]).int
else { throw BencoderError.invalidNumber }
return (bencode: .integer(num), index: end+1)
}

func parseString(_ data: ArraySlice<Byte>, from index: Int) throws -> ParseResult {
guard let sep = data.index(of: tokens.colon)
else { throw BencoderError.tokenNotFound(tokens.colon) }
guard let len = Array(data[..<sep]).int
else { throw BencoderError.invalidNumber }
let start = sep + 1
let end = data.index(start, offsetBy: len)
return (bencode: .string(Array(data[start..<end])), index: end)
}

func parseList(_ data: ArraySlice<Byte>, 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<Byte>, 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)
}
}
16 changes: 16 additions & 0 deletions Source/BencoderError.swift
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit fa54fd9

Please sign in to comment.