Skip to content

Commit

Permalink
Add more tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Qata committed Jan 27, 2017
1 parent 5e3bb20 commit 2280d8f
Show file tree
Hide file tree
Showing 12 changed files with 205 additions and 93 deletions.
32 changes: 24 additions & 8 deletions BencodeKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,18 @@
objects = {

/* Begin PBXBuildFile section */
9EA794CA1E3B0E44000D6C50 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA794C91E3B0E44000D6C50 /* Extensions.swift */; };
9EA794CD1E3B0F92000D6C50 /* Bencode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798331E3AEE0B005A7C9E /* Bencode.swift */; };
9EA794CE1E3B1691000D6C50 /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798391E3AEE1F005A7C9E /* String.swift */; };
9EA794CF1E3B1D42000D6C50 /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798381E3AEE1F005A7C9E /* List.swift */; };
9EA794D01E3B1E0F000D6C50 /* Decode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EA794CB1E3B0F82000D6C50 /* Decode.swift */; };
9EA794D11E3B1E8F000D6C50 /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798361E3AEE1F005A7C9E /* Dictionary.swift */; };
9EB798241E3AE7EA005A7C9E /* BencodeKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9EB7981A1E3AE7EA005A7C9E /* BencodeKit.framework */; };
9EB798291E3AE7EA005A7C9E /* Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798281E3AE7EA005A7C9E /* Tests.swift */; };
9EB7982B1E3AE7EA005A7C9E /* BencodeKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 9EB7981D1E3AE7EA005A7C9E /* BencodeKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
9EB798341E3AEE0B005A7C9E /* Bencode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798331E3AEE0B005A7C9E /* Bencode.swift */; };
9EB7983A1E3AEE1F005A7C9E /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798361E3AEE1F005A7C9E /* Dictionary.swift */; };
9EB7983B1E3AEE1F005A7C9E /* Integer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798371E3AEE1F005A7C9E /* Integer.swift */; };
9EB7983C1E3AEE1F005A7C9E /* List.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798381E3AEE1F005A7C9E /* List.swift */; };
9EB7983D1E3AEE1F005A7C9E /* String.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EB798391E3AEE1F005A7C9E /* String.swift */; };
9EB7983F1E3AF9C3005A7C9E /* Torrents in Resources */ = {isa = PBXBuildFile; fileRef = 9EB7983E1E3AF9C3005A7C9E /* Torrents */; };
9EB798401E3AF9C5005A7C9E /* Torrents in Resources */ = {isa = PBXBuildFile; fileRef = 9EB7983E1E3AF9C3005A7C9E /* Torrents */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand All @@ -28,6 +32,8 @@
/* End PBXContainerItemProxy section */

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

/* Begin PBXFrameworksBuildPhase section */
Expand All @@ -64,6 +71,7 @@
isa = PBXGroup;
children = (
9EB798321E3AE884005A7C9E /* Sources */,
9EA794C91E3B0E44000D6C50 /* Extensions.swift */,
9EB798271E3AE7EA005A7C9E /* Tests */,
9EB7981B1E3AE7EA005A7C9E /* Products */,
);
Expand All @@ -83,6 +91,7 @@
children = (
9EB7982A1E3AE7EA005A7C9E /* Info.plist */,
9EB798281E3AE7EA005A7C9E /* Tests.swift */,
9EB7983E1E3AF9C3005A7C9E /* Torrents */,
);
path = Tests;
sourceTree = "<group>";
Expand All @@ -93,6 +102,7 @@
9EB7981E1E3AE7EA005A7C9E /* Info.plist */,
9EB7981D1E3AE7EA005A7C9E /* BencodeKit.h */,
9EB798331E3AEE0B005A7C9E /* Bencode.swift */,
9EA794CB1E3B0F82000D6C50 /* Decode.swift */,
9EB798351E3AEE1F005A7C9E /* Types */,
);
path = Sources;
Expand Down Expand Up @@ -202,13 +212,15 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9EB7983F1E3AF9C3005A7C9E /* Torrents in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
9EB798211E3AE7EA005A7C9E /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9EB798401E3AF9C5005A7C9E /* Torrents in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -219,11 +231,13 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9EA794CD1E3B0F92000D6C50 /* Bencode.swift in Sources */,
9EA794D01E3B1E0F000D6C50 /* Decode.swift in Sources */,
9EB7983B1E3AEE1F005A7C9E /* Integer.swift in Sources */,
9EB7983C1E3AEE1F005A7C9E /* List.swift in Sources */,
9EB7983D1E3AEE1F005A7C9E /* String.swift in Sources */,
9EB798341E3AEE0B005A7C9E /* Bencode.swift in Sources */,
9EB7983A1E3AEE1F005A7C9E /* Dictionary.swift in Sources */,
9EA794CE1E3B1691000D6C50 /* String.swift in Sources */,
9EA794D11E3B1E8F000D6C50 /* Dictionary.swift in Sources */,
9EA794CF1E3B1D42000D6C50 /* List.swift in Sources */,
9EA794CA1E3B0E44000D6C50 /* Extensions.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -511,6 +525,7 @@
9EB7982E1E3AE7EA005A7C9E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
9EB7982F1E3AE7EA005A7C9E /* Build configuration list for PBXNativeTarget "BencodeKit-Tests" */ = {
isa = XCConfigurationList;
Expand All @@ -519,6 +534,7 @@
9EB798311E3AE7EA005A7C9E /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
Expand Down
102 changes: 59 additions & 43 deletions Sources/Bencode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,80 @@

public enum BencodingError: Error {
case emptyString
case unexpectedCharacter(Character, String.Index)
case negativeZeroEncountered(String.Index)
case invalidStringLength(String.Index)
case invalidInteger(String.Index)
case contaminatedInteger(String.Index)
case unterminatedInteger(String.Index)
case invalidList(String.Index)
case unterminatedList(String.Index)
case invalidDictionary(String.Index)
case nonStringDictionaryKey(String.Index)
case unterminatedDictionary(String.Index)
case unexpectedCharacter(Character, Data.Index)
case negativeZeroEncountered(Data.Index)
case invalidStringLength(Data.Index)
case invalidInteger(Data.Index)
case contaminatedInteger(Data.Index)
case unterminatedInteger(Data.Index)
case invalidList(Data.Index)
case unterminatedList(Data.Index)
case invalidDictionary(Data.Index)
case nonStringDictionaryKey(Data.Index)
case nonAsciiDictionaryKey(Data.Index)
case unterminatedDictionary(Data.Index)
}

public indirect enum Bencode: Equatable {
case integer(Int)
case string(String)
case bytes(Data)
case list([Bencode])
case dictionary([(String, Bencode)])

init(_ integer: Int) {
self = .integer(integer)
}

init?(_ string: String) {
guard let value = string.data(using: .ascii) else {
return nil
}
self = .bytes(value)
}

init(_ bytes: Data) {
self = .bytes(bytes)
}

init(_ list: [Bencode]) {
self = .list(list)
}

init(_ dictionary: [(String, Bencode)]) {
self = .dictionary(dictionary)
}

var description: String {
switch self {
case .integer(let integer):
return "i\(String(integer))e"
case .bytes(let bytes):
return "\(bytes.count):\(String(bytes: bytes, encoding: .ascii) ?? "")"
case .list(let list):
return "l\(list.map({ $0.description }).joined())e"
case .dictionary(let dictionary):
return "d\(dictionary.map({ "\($0.characters.count):\($0)\($1.description)" }).joined())e"
}
}
}

public extension Bencode {
public static func decode(_ string: String) throws -> Bencode {
return try bdecode(string, string.startIndex).match
public static func decode(_ data: Data) throws -> Bencode {
return try bdecode(data, data.startIndex).match
}
}

public extension Bencode {
public func encoded() -> String {
public func encoded() -> Data {
switch self {
case .integer(let integer):
return "i\(String(integer))e"
case .string(let string):
return "\(string.characters.count):\(string)"
return "i\(String(integer))e".asciiData
case .bytes(let bytes):
return "\(bytes.count):".asciiData + bytes
case .list(let list):
return "l\(list.map({ $0.encoded() }).joined())e"
return "l".asciiData + list.map({ $0.encoded() }).joined() + "e".asciiData
case .dictionary(let dictionary):
return "d\(dictionary.map({ "\($0.characters.count):\($0)\($1.encoded())" }).joined())e"
return "d".asciiData + dictionary.map({ "\($0.characters.count):\($0)".asciiData + $1.encoded() }).joined() + "e".asciiData
}
}
}
Expand All @@ -54,35 +91,14 @@ public extension Bencode {
switch (lhs, rhs) {
case (.integer(let a), .integer(let b)):
return a == b
case (.string(let a), .string(let b)):
case (.bytes(let a), .bytes(let b)):
return a == b
case (.list(let a), .list(let b)):
return a.count == b.count && zip(a, b).reduce(true, { $0 && ($1.0 == $1.1) })
case (.dictionary(let a), .dictionary(let b)):
return a.count == b.count && zip(a, b).reduce(true, { $0 && ($1.0 == $1.1) })
case (_, _):
case _:
return false
}
}
}

internal func bdecode(_ string: String, _ index: String.Index) throws -> (match: Bencode, index: String.Index) {
guard !string.isEmpty else {
throw BencodingError.emptyString
}

let character = string[index]

switch character {
case "d":
return try bdecodeDictionary(string, index)
case "i":
return try bdecodeInteger(string, index)
case "0"..."9":
return try bdecodeString(string, index)
case "l":
return try bdecodeList(string, index)
default:
throw BencodingError.unexpectedCharacter(character, index)
}
}
28 changes: 28 additions & 0 deletions Sources/Decode.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// Decode.swift
// BencodeKit
//
// Created by Charlotte Tortorella on 27/1/17.
//
//

internal func bdecode(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) {
guard !data.isEmpty else {
throw BencodingError.emptyString
}

let character = data[index]

switch character {
case UInt8("d"):
return try bdecodeDictionary(data, index)
case UInt8("i"):
return try bdecodeInteger(data, index)
case UInt8("0")...UInt8("9"):
return try bdecodeString(data, index)
case UInt8("l"):
return try bdecodeList(data, index)
default:
throw BencodingError.unexpectedCharacter(Character(UnicodeScalar(character)), index)
}
}
25 changes: 14 additions & 11 deletions Sources/Types/Dictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,33 @@
// Copyright © 2017 Monadic Consulting. All rights reserved.
//

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

var currentIndex = string.index(after: index)
guard currentIndex != string.endIndex else {
var currentIndex = data.index(after: index)
guard currentIndex != data.endIndex else {
throw BencodingError.unterminatedDictionary(index)
}

var values: [(String, Bencode)] = []
while string[currentIndex] != "e" {
let (keyMatch, valueIndex) = try bdecode(string, currentIndex)
guard case .string(let key) = keyMatch else {
while !(data[currentIndex] == "e") {
let (keyMatch, valueIndex) = try bdecode(data, currentIndex)
guard case .bytes(let key) = keyMatch else {
throw BencodingError.nonStringDictionaryKey(currentIndex)
}

let (valueMatch, nextIndex) = try bdecode(string, valueIndex)
values.append((key, valueMatch))
let (valueMatch, nextIndex) = try bdecode(data, valueIndex)
guard let stringKey = String(bytes: key, encoding: .ascii) else {
throw BencodingError.nonAsciiDictionaryKey(currentIndex)
}
values.append(stringKey, valueMatch)
currentIndex = nextIndex
guard currentIndex != string.endIndex else {
guard currentIndex != data.endIndex else {
throw BencodingError.unterminatedDictionary(index)
}
}

return (.dictionary(values), string.index(after: currentIndex))
return (.dictionary(values), data.index(after: currentIndex))
}
27 changes: 27 additions & 0 deletions Sources/Types/Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//
// Extensions.swift
// BencodeKit
//
// Created by Charlotte Tortorella on 27/1/17.
//
//

func ==(lhs: Character, rhs: UInt8) -> Bool {
return lhs == Character(UnicodeScalar(rhs))
}

func ==(lhs: UInt8, rhs: Character) -> Bool {
return rhs == lhs
}

internal extension UInt8 {
init(_ character: String) {
self = UInt8(character.unicodeScalars.first!.value)
}
}

internal extension String {
var asciiData: Data {
return data(using: .ascii)!
}
}
19 changes: 10 additions & 9 deletions Sources/Types/Integer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@
// Copyright © 2017 Monadic Consulting. All rights reserved.
//

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

let integerIndex = string.index(after: index)
guard let (eOffset, _) = string.substring(from: index).characters.enumerated().first(where: { $1 == "e" }) else {
let integerIndex = data.index(after: index)
guard let (eOffset, _) = data.subdata(in: Range(uncheckedBounds: (index, data.endIndex))).enumerated().first(where: { $1 == "e" }) else {
throw BencodingError.unterminatedInteger(index)
}

let eIndex = string.index(index, offsetBy: eOffset)
let integerString = string.substring(with: Range(uncheckedBounds: (integerIndex, eIndex)))
guard !integerString.hasPrefix("-0") else {
let eIndex = data.index(index, offsetBy: eOffset)
let integerData = data.subdata(in: Range(uncheckedBounds: (integerIndex, eIndex)))
guard let integerString = String(bytes: integerData, encoding: .ascii), !integerString.hasPrefix("-0") else {
throw BencodingError.negativeZeroEncountered(index)
}

print(integerString)

guard let value = Int(integerString) else {
throw BencodingError.contaminatedInteger(index)
}

return (.integer(value), string.index(after: eIndex))
return (.integer(value), data.index(after: eIndex))
}
Loading

0 comments on commit 2280d8f

Please sign in to comment.