Skip to content

Commit 2280d8f

Browse files
committed
Add more tests
1 parent 5e3bb20 commit 2280d8f

File tree

12 files changed

+205
-93
lines changed

12 files changed

+205
-93
lines changed

BencodeKit.xcodeproj/project.pbxproj

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
9EA794CA1E3B0E44000D6C50 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA794C91E3B0E44000D6C50 /* Extensions.swift */; };
11+
9EA794CD1E3B0F92000D6C50 /* Bencode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798331E3AEE0B005A7C9E /* Bencode.swift */; };
12+
9EA794CE1E3B1691000D6C50 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798391E3AEE1F005A7C9E /* String.swift */; };
13+
9EA794CF1E3B1D42000D6C50 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798381E3AEE1F005A7C9E /* List.swift */; };
14+
9EA794D01E3B1E0F000D6C50 /* Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA794CB1E3B0F82000D6C50 /* Decode.swift */; };
15+
9EA794D11E3B1E8F000D6C50 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798361E3AEE1F005A7C9E /* Dictionary.swift */; };
1016
9EB798241E3AE7EA005A7C9E /* BencodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EB7981A1E3AE7EA005A7C9E /* BencodeKit.framework */; };
1117
9EB798291E3AE7EA005A7C9E /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798281E3AE7EA005A7C9E /* Tests.swift */; };
1218
9EB7982B1E3AE7EA005A7C9E /* BencodeKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 9EB7981D1E3AE7EA005A7C9E /* BencodeKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
13-
9EB798341E3AEE0B005A7C9E /* Bencode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798331E3AEE0B005A7C9E /* Bencode.swift */; };
14-
9EB7983A1E3AEE1F005A7C9E /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798361E3AEE1F005A7C9E /* Dictionary.swift */; };
1519
9EB7983B1E3AEE1F005A7C9E /* Integer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798371E3AEE1F005A7C9E /* Integer.swift */; };
16-
9EB7983C1E3AEE1F005A7C9E /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798381E3AEE1F005A7C9E /* List.swift */; };
17-
9EB7983D1E3AEE1F005A7C9E /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798391E3AEE1F005A7C9E /* String.swift */; };
20+
9EB7983F1E3AF9C3005A7C9E /* Torrents in Resources */ = {isa = PBXBuildFile; fileRef = 9EB7983E1E3AF9C3005A7C9E /* Torrents */; };
21+
9EB798401E3AF9C5005A7C9E /* Torrents in Resources */ = {isa = PBXBuildFile; fileRef = 9EB7983E1E3AF9C3005A7C9E /* Torrents */; };
1822
/* End PBXBuildFile section */
1923

2024
/* Begin PBXContainerItemProxy section */
@@ -28,6 +32,8 @@
2832
/* End PBXContainerItemProxy section */
2933

3034
/* Begin PBXFileReference section */
35+
9EA794C91E3B0E44000D6C50 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Extensions.swift; path = Sources/Types/Extensions.swift; sourceTree = "<group>"; };
36+
9EA794CB1E3B0F82000D6C50 /* Decode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Decode.swift; sourceTree = "<group>"; };
3137
9EB7981A1E3AE7EA005A7C9E /* BencodeKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BencodeKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3238
9EB7981D1E3AE7EA005A7C9E /* BencodeKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BencodeKit.h; sourceTree = "<group>"; };
3339
9EB7981E1E3AE7EA005A7C9E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
@@ -39,6 +45,7 @@
3945
9EB798371E3AEE1F005A7C9E /* Integer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Integer.swift; sourceTree = "<group>"; };
4046
9EB798381E3AEE1F005A7C9E /* List.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = List.swift; sourceTree = "<group>"; };
4147
9EB798391E3AEE1F005A7C9E /* String.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = String.swift; sourceTree = "<group>"; };
48+
9EB7983E1E3AF9C3005A7C9E /* Torrents */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Torrents; sourceTree = "<group>"; };
4249
/* End PBXFileReference section */
4350

4451
/* Begin PBXFrameworksBuildPhase section */
@@ -64,6 +71,7 @@
6471
isa = PBXGroup;
6572
children = (
6673
9EB798321E3AE884005A7C9E /* Sources */,
74+
9EA794C91E3B0E44000D6C50 /* Extensions.swift */,
6775
9EB798271E3AE7EA005A7C9E /* Tests */,
6876
9EB7981B1E3AE7EA005A7C9E /* Products */,
6977
);
@@ -83,6 +91,7 @@
8391
children = (
8492
9EB7982A1E3AE7EA005A7C9E /* Info.plist */,
8593
9EB798281E3AE7EA005A7C9E /* Tests.swift */,
94+
9EB7983E1E3AF9C3005A7C9E /* Torrents */,
8695
);
8796
path = Tests;
8897
sourceTree = "<group>";
@@ -93,6 +102,7 @@
93102
9EB7981E1E3AE7EA005A7C9E /* Info.plist */,
94103
9EB7981D1E3AE7EA005A7C9E /* BencodeKit.h */,
95104
9EB798331E3AEE0B005A7C9E /* Bencode.swift */,
105+
9EA794CB1E3B0F82000D6C50 /* Decode.swift */,
96106
9EB798351E3AEE1F005A7C9E /* Types */,
97107
);
98108
path = Sources;
@@ -202,13 +212,15 @@
202212
isa = PBXResourcesBuildPhase;
203213
buildActionMask = 2147483647;
204214
files = (
215+
9EB7983F1E3AF9C3005A7C9E /* Torrents in Resources */,
205216
);
206217
runOnlyForDeploymentPostprocessing = 0;
207218
};
208219
9EB798211E3AE7EA005A7C9E /* Resources */ = {
209220
isa = PBXResourcesBuildPhase;
210221
buildActionMask = 2147483647;
211222
files = (
223+
9EB798401E3AF9C5005A7C9E /* Torrents in Resources */,
212224
);
213225
runOnlyForDeploymentPostprocessing = 0;
214226
};
@@ -219,11 +231,13 @@
219231
isa = PBXSourcesBuildPhase;
220232
buildActionMask = 2147483647;
221233
files = (
234+
9EA794CD1E3B0F92000D6C50 /* Bencode.swift in Sources */,
235+
9EA794D01E3B1E0F000D6C50 /* Decode.swift in Sources */,
222236
9EB7983B1E3AEE1F005A7C9E /* Integer.swift in Sources */,
223-
9EB7983C1E3AEE1F005A7C9E /* List.swift in Sources */,
224-
9EB7983D1E3AEE1F005A7C9E /* String.swift in Sources */,
225-
9EB798341E3AEE0B005A7C9E /* Bencode.swift in Sources */,
226-
9EB7983A1E3AEE1F005A7C9E /* Dictionary.swift in Sources */,
237+
9EA794CE1E3B1691000D6C50 /* String.swift in Sources */,
238+
9EA794D11E3B1E8F000D6C50 /* Dictionary.swift in Sources */,
239+
9EA794CF1E3B1D42000D6C50 /* List.swift in Sources */,
240+
9EA794CA1E3B0E44000D6C50 /* Extensions.swift in Sources */,
227241
);
228242
runOnlyForDeploymentPostprocessing = 0;
229243
};
@@ -511,6 +525,7 @@
511525
9EB7982E1E3AE7EA005A7C9E /* Release */,
512526
);
513527
defaultConfigurationIsVisible = 0;
528+
defaultConfigurationName = Release;
514529
};
515530
9EB7982F1E3AE7EA005A7C9E /* Build configuration list for PBXNativeTarget "BencodeKit-Tests" */ = {
516531
isa = XCConfigurationList;
@@ -519,6 +534,7 @@
519534
9EB798311E3AE7EA005A7C9E /* Release */,
520535
);
521536
defaultConfigurationIsVisible = 0;
537+
defaultConfigurationName = Release;
522538
};
523539
/* End XCConfigurationList section */
524540
};

Sources/Bencode.swift

Lines changed: 59 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -8,43 +8,80 @@
88

99
public enum BencodingError: Error {
1010
case emptyString
11-
case unexpectedCharacter(Character, String.Index)
12-
case negativeZeroEncountered(String.Index)
13-
case invalidStringLength(String.Index)
14-
case invalidInteger(String.Index)
15-
case contaminatedInteger(String.Index)
16-
case unterminatedInteger(String.Index)
17-
case invalidList(String.Index)
18-
case unterminatedList(String.Index)
19-
case invalidDictionary(String.Index)
20-
case nonStringDictionaryKey(String.Index)
21-
case unterminatedDictionary(String.Index)
11+
case unexpectedCharacter(Character, Data.Index)
12+
case negativeZeroEncountered(Data.Index)
13+
case invalidStringLength(Data.Index)
14+
case invalidInteger(Data.Index)
15+
case contaminatedInteger(Data.Index)
16+
case unterminatedInteger(Data.Index)
17+
case invalidList(Data.Index)
18+
case unterminatedList(Data.Index)
19+
case invalidDictionary(Data.Index)
20+
case nonStringDictionaryKey(Data.Index)
21+
case nonAsciiDictionaryKey(Data.Index)
22+
case unterminatedDictionary(Data.Index)
2223
}
2324

2425
public indirect enum Bencode: Equatable {
2526
case integer(Int)
26-
case string(String)
27+
case bytes(Data)
2728
case list([Bencode])
2829
case dictionary([(String, Bencode)])
30+
31+
init(_ integer: Int) {
32+
self = .integer(integer)
33+
}
34+
35+
init?(_ string: String) {
36+
guard let value = string.data(using: .ascii) else {
37+
return nil
38+
}
39+
self = .bytes(value)
40+
}
41+
42+
init(_ bytes: Data) {
43+
self = .bytes(bytes)
44+
}
45+
46+
init(_ list: [Bencode]) {
47+
self = .list(list)
48+
}
49+
50+
init(_ dictionary: [(String, Bencode)]) {
51+
self = .dictionary(dictionary)
52+
}
53+
54+
var description: String {
55+
switch self {
56+
case .integer(let integer):
57+
return "i\(String(integer))e"
58+
case .bytes(let bytes):
59+
return "\(bytes.count):\(String(bytes: bytes, encoding: .ascii) ?? "")"
60+
case .list(let list):
61+
return "l\(list.map({ $0.description }).joined())e"
62+
case .dictionary(let dictionary):
63+
return "d\(dictionary.map({ "\($0.characters.count):\($0)\($1.description)" }).joined())e"
64+
}
65+
}
2966
}
3067

3168
public extension Bencode {
32-
public static func decode(_ string: String) throws -> Bencode {
33-
return try bdecode(string, string.startIndex).match
69+
public static func decode(_ data: Data) throws -> Bencode {
70+
return try bdecode(data, data.startIndex).match
3471
}
3572
}
3673

3774
public extension Bencode {
38-
public func encoded() -> String {
75+
public func encoded() -> Data {
3976
switch self {
4077
case .integer(let integer):
41-
return "i\(String(integer))e"
42-
case .string(let string):
43-
return "\(string.characters.count):\(string)"
78+
return "i\(String(integer))e".asciiData
79+
case .bytes(let bytes):
80+
return "\(bytes.count):".asciiData + bytes
4481
case .list(let list):
45-
return "l\(list.map({ $0.encoded() }).joined())e"
82+
return "l".asciiData + list.map({ $0.encoded() }).joined() + "e".asciiData
4683
case .dictionary(let dictionary):
47-
return "d\(dictionary.map({ "\($0.characters.count):\($0)\($1.encoded())" }).joined())e"
84+
return "d".asciiData + dictionary.map({ "\($0.characters.count):\($0)".asciiData + $1.encoded() }).joined() + "e".asciiData
4885
}
4986
}
5087
}
@@ -54,35 +91,14 @@ public extension Bencode {
5491
switch (lhs, rhs) {
5592
case (.integer(let a), .integer(let b)):
5693
return a == b
57-
case (.string(let a), .string(let b)):
94+
case (.bytes(let a), .bytes(let b)):
5895
return a == b
5996
case (.list(let a), .list(let b)):
6097
return a.count == b.count && zip(a, b).reduce(true, { $0 && ($1.0 == $1.1) })
6198
case (.dictionary(let a), .dictionary(let b)):
6299
return a.count == b.count && zip(a, b).reduce(true, { $0 && ($1.0 == $1.1) })
63-
case (_, _):
100+
case _:
64101
return false
65102
}
66103
}
67104
}
68-
69-
internal func bdecode(_ string: String, _ index: String.Index) throws -> (match: Bencode, index: String.Index) {
70-
guard !string.isEmpty else {
71-
throw BencodingError.emptyString
72-
}
73-
74-
let character = string[index]
75-
76-
switch character {
77-
case "d":
78-
return try bdecodeDictionary(string, index)
79-
case "i":
80-
return try bdecodeInteger(string, index)
81-
case "0"..."9":
82-
return try bdecodeString(string, index)
83-
case "l":
84-
return try bdecodeList(string, index)
85-
default:
86-
throw BencodingError.unexpectedCharacter(character, index)
87-
}
88-
}

Sources/Decode.swift

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Decode.swift
3+
// BencodeKit
4+
//
5+
// Created by Charlotte Tortorella on 27/1/17.
6+
//
7+
//
8+
9+
internal func bdecode(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) {
10+
guard !data.isEmpty else {
11+
throw BencodingError.emptyString
12+
}
13+
14+
let character = data[index]
15+
16+
switch character {
17+
case UInt8("d"):
18+
return try bdecodeDictionary(data, index)
19+
case UInt8("i"):
20+
return try bdecodeInteger(data, index)
21+
case UInt8("0")...UInt8("9"):
22+
return try bdecodeString(data, index)
23+
case UInt8("l"):
24+
return try bdecodeList(data, index)
25+
default:
26+
throw BencodingError.unexpectedCharacter(Character(UnicodeScalar(character)), index)
27+
}
28+
}

Sources/Types/Dictionary.swift

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,33 @@
66
// Copyright © 2017 Monadic Consulting. All rights reserved.
77
//
88

9-
func bdecodeDictionary(_ string: String, _ index: String.Index) throws -> (Bencode, String.Index) {
10-
guard string[index] == "d" else {
9+
func bdecodeDictionary(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) {
10+
guard data[index] == "d" else {
1111
throw BencodingError.invalidDictionary(index)
1212
}
1313

14-
var currentIndex = string.index(after: index)
15-
guard currentIndex != string.endIndex else {
14+
var currentIndex = data.index(after: index)
15+
guard currentIndex != data.endIndex else {
1616
throw BencodingError.unterminatedDictionary(index)
1717
}
1818

1919
var values: [(String, Bencode)] = []
20-
while string[currentIndex] != "e" {
21-
let (keyMatch, valueIndex) = try bdecode(string, currentIndex)
22-
guard case .string(let key) = keyMatch else {
20+
while !(data[currentIndex] == "e") {
21+
let (keyMatch, valueIndex) = try bdecode(data, currentIndex)
22+
guard case .bytes(let key) = keyMatch else {
2323
throw BencodingError.nonStringDictionaryKey(currentIndex)
2424
}
2525

26-
let (valueMatch, nextIndex) = try bdecode(string, valueIndex)
27-
values.append((key, valueMatch))
26+
let (valueMatch, nextIndex) = try bdecode(data, valueIndex)
27+
guard let stringKey = String(bytes: key, encoding: .ascii) else {
28+
throw BencodingError.nonAsciiDictionaryKey(currentIndex)
29+
}
30+
values.append(stringKey, valueMatch)
2831
currentIndex = nextIndex
29-
guard currentIndex != string.endIndex else {
32+
guard currentIndex != data.endIndex else {
3033
throw BencodingError.unterminatedDictionary(index)
3134
}
3235
}
3336

34-
return (.dictionary(values), string.index(after: currentIndex))
37+
return (.dictionary(values), data.index(after: currentIndex))
3538
}

Sources/Types/Extensions.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//
2+
// Extensions.swift
3+
// BencodeKit
4+
//
5+
// Created by Charlotte Tortorella on 27/1/17.
6+
//
7+
//
8+
9+
func ==(lhs: Character, rhs: UInt8) -> Bool {
10+
return lhs == Character(UnicodeScalar(rhs))
11+
}
12+
13+
func ==(lhs: UInt8, rhs: Character) -> Bool {
14+
return rhs == lhs
15+
}
16+
17+
internal extension UInt8 {
18+
init(_ character: String) {
19+
self = UInt8(character.unicodeScalars.first!.value)
20+
}
21+
}
22+
23+
internal extension String {
24+
var asciiData: Data {
25+
return data(using: .ascii)!
26+
}
27+
}

Sources/Types/Integer.swift

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,26 @@
66
// Copyright © 2017 Monadic Consulting. All rights reserved.
77
//
88

9-
func bdecodeInteger(_ string: String, _ index: String.Index) throws -> (match: Bencode, index: String.Index) {
10-
guard string[index] == "i" else {
9+
func bdecodeInteger(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) {
10+
guard data[index] == "i" else {
1111
throw BencodingError.invalidInteger(index)
1212
}
1313

14-
let integerIndex = string.index(after: index)
15-
guard let (eOffset, _) = string.substring(from: index).characters.enumerated().first(where: { $1 == "e" }) else {
14+
let integerIndex = data.index(after: index)
15+
guard let (eOffset, _) = data.subdata(in: Range(uncheckedBounds: (index, data.endIndex))).enumerated().first(where: { $1 == "e" }) else {
1616
throw BencodingError.unterminatedInteger(index)
1717
}
18-
19-
let eIndex = string.index(index, offsetBy: eOffset)
20-
let integerString = string.substring(with: Range(uncheckedBounds: (integerIndex, eIndex)))
21-
guard !integerString.hasPrefix("-0") else {
18+
let eIndex = data.index(index, offsetBy: eOffset)
19+
let integerData = data.subdata(in: Range(uncheckedBounds: (integerIndex, eIndex)))
20+
guard let integerString = String(bytes: integerData, encoding: .ascii), !integerString.hasPrefix("-0") else {
2221
throw BencodingError.negativeZeroEncountered(index)
2322
}
2423

24+
print(integerString)
25+
2526
guard let value = Int(integerString) else {
2627
throw BencodingError.contaminatedInteger(index)
2728
}
2829

29-
return (.integer(value), string.index(after: eIndex))
30+
return (.integer(value), data.index(after: eIndex))
3031
}

0 commit comments

Comments
 (0)