Created TorrentPeerCommunicator class and implemented all basic send methods for the Peer Wire Protocol
This commit is contained in:
@@ -17,6 +17,11 @@
|
||||
B51638971F0EEC2B009E563E /* GCDAsyncSocketStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51638961F0EEC2B009E563E /* GCDAsyncSocketStub.swift */; };
|
||||
B51D6C091F0C180600E1E3AB /* TorrentUDPTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51D6C031F0C17AE00E1E3AB /* TorrentUDPTrackerTests.swift */; };
|
||||
B51D6C0A1F0C180D00E1E3AB /* TorrentUDPTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51D6C061F0C17C000E1E3AB /* TorrentUDPTracker.swift */; };
|
||||
B53553361F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53553351F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift */; };
|
||||
B535533A1F0FEB9700A6DCBE /* TorrentPeerCommunicatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53553381F0FEB9300A6DCBE /* TorrentPeerCommunicatorTests.swift */; };
|
||||
B535533C1F0FEE6300A6DCBE /* TCPConnectionStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B535533B1F0FEE6300A6DCBE /* TCPConnectionStub.swift */; };
|
||||
B535533E1F11261200A6DCBE /* BitField.swift in Sources */ = {isa = PBXBuildFile; fileRef = B535533D1F11261200A6DCBE /* BitField.swift */; };
|
||||
B53553401F112BDB00A6DCBE /* BitFieldTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B535533F1F112BDB00A6DCBE /* BitFieldTests.swift */; };
|
||||
B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B537CF051F03148B0084089B /* HTTPConnectionStub.swift */; };
|
||||
B537CF461F031AD20084089B /* TestText.torrent in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0E31F02F9E700EF58E3 /* TestText.torrent */; };
|
||||
B537CF491F031C3D0084089B /* BEncode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B54D0C1A1CA53983004343BD /* BEncode.framework */; };
|
||||
@@ -130,10 +135,15 @@
|
||||
B51638961F0EEC2B009E563E /* GCDAsyncSocketStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GCDAsyncSocketStub.swift; sourceTree = "<group>"; };
|
||||
B51D6C031F0C17AE00E1E3AB /* TorrentUDPTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentUDPTrackerTests.swift; sourceTree = "<group>"; };
|
||||
B51D6C061F0C17C000E1E3AB /* TorrentUDPTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentUDPTracker.swift; sourceTree = "<group>"; };
|
||||
B53553351F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerCommunicator.swift; sourceTree = "<group>"; };
|
||||
B53553381F0FEB9300A6DCBE /* TorrentPeerCommunicatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerCommunicatorTests.swift; sourceTree = "<group>"; };
|
||||
B535533B1F0FEE6300A6DCBE /* TCPConnectionStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TCPConnectionStub.swift; sourceTree = "<group>"; };
|
||||
B535533D1F11261200A6DCBE /* BitField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitField.swift; sourceTree = "<group>"; };
|
||||
B535533F1F112BDB00A6DCBE /* BitFieldTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BitFieldTests.swift; sourceTree = "<group>"; };
|
||||
B537CF051F03148B0084089B /* HTTPConnectionStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnectionStub.swift; path = "HTTP Networking/HTTPConnectionStub.swift"; sourceTree = "<group>"; };
|
||||
B54D0C141CA53983004343BD /* BEncode.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BEncode.xcodeproj; path = Submodules/BEncodeSwift/BEncode.xcodeproj; sourceTree = "<group>"; };
|
||||
B54D0C231CA56A22004343BD /* TorrentMetaInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TorrentMetaInfo.swift; path = Models/TorrentMetaInfo.swift; sourceTree = "<group>"; };
|
||||
B54D0C261CA56ADB004343BD /* TorrentMetaInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TorrentMetaInfoTests.swift; path = Models/TorrentMetaInfoTests.swift; sourceTree = "<group>"; };
|
||||
B54D0C231CA56A22004343BD /* TorrentMetaInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TorrentMetaInfo.swift; sourceTree = "<group>"; };
|
||||
B54D0C261CA56ADB004343BD /* TorrentMetaInfoTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TorrentMetaInfoTests.swift; sourceTree = "<group>"; };
|
||||
B54D0C2B1CA5787E004343BD /* Data+sha1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Data+sha1.swift"; path = "Utilities/Data+sha1.swift"; sourceTree = "<group>"; };
|
||||
B54D0C581CA58722004343BD /* CommonCrypto.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CommonCrypto.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B54D0C6F1CA58749004343BD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -243,6 +253,7 @@
|
||||
children = (
|
||||
B51638921F0EE9B6009E563E /* TCPConnection.swift */,
|
||||
B51638941F0EEAA4009E563E /* TCPConnectionTests.swift */,
|
||||
B535533B1F0FEE6300A6DCBE /* TCPConnectionStub.swift */,
|
||||
B51638961F0EEC2B009E563E /* GCDAsyncSocketStub.swift */,
|
||||
);
|
||||
path = "TCP Networking";
|
||||
@@ -281,8 +292,10 @@
|
||||
children = (
|
||||
B54D0C231CA56A22004343BD /* TorrentMetaInfo.swift */,
|
||||
B54D0C261CA56ADB004343BD /* TorrentMetaInfoTests.swift */,
|
||||
B535533D1F11261200A6DCBE /* BitField.swift */,
|
||||
B535533F1F112BDB00A6DCBE /* BitFieldTests.swift */,
|
||||
);
|
||||
name = Models;
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B54D0C291CA5785F004343BD /* Utilities */ = {
|
||||
@@ -405,6 +418,8 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5F81E481F0436D600B25C70 /* TorrentPeerInfo.swift */,
|
||||
B53553351F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift */,
|
||||
B53553381F0FEB9300A6DCBE /* TorrentPeerCommunicatorTests.swift */,
|
||||
);
|
||||
path = Peer;
|
||||
sourceTree = "<group>";
|
||||
@@ -765,6 +780,8 @@
|
||||
B54D0C7A1CA69FAD004343BD /* TorrentMetaInfo.swift in Sources */,
|
||||
B5F81E4B1F04399800B25C70 /* TorrentTrackerResponse.swift in Sources */,
|
||||
B54D0C7B1CA69FD8004343BD /* Data+sha1.swift in Sources */,
|
||||
B53553361F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift in Sources */,
|
||||
B535533E1F11261200A6DCBE /* BitField.swift in Sources */,
|
||||
B5530DB21F03063300F71CCD /* HTTPConnection.swift in Sources */,
|
||||
B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */,
|
||||
B51D6C0A1F0C180D00E1E3AB /* TorrentUDPTracker.swift in Sources */,
|
||||
@@ -776,14 +793,17 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B535533A1F0FEB9700A6DCBE /* TorrentPeerCommunicatorTests.swift in Sources */,
|
||||
B51638971F0EEC2B009E563E /* GCDAsyncSocketStub.swift in Sources */,
|
||||
B51D6C091F0C180600E1E3AB /* TorrentUDPTrackerTests.swift in Sources */,
|
||||
B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */,
|
||||
B5BD7FD61F03032400621BC2 /* TorrentHTTPTrackerTests.swift in Sources */,
|
||||
B556D5AE1F0BA1FD00277B8D /* GCDAsyncUdpSocketStub.swift in Sources */,
|
||||
B5530DB51F03063E00F71CCD /* HTTPConnectionTests.swift in Sources */,
|
||||
B535533C1F0FEE6300A6DCBE /* TCPConnectionStub.swift in Sources */,
|
||||
B56A8A071C83539300426AC8 /* TestHelpers.swift in Sources */,
|
||||
B585AB841C3833450093FA41 /* BitTorrentTests.swift in Sources */,
|
||||
B53553401F112BDB00A6DCBE /* BitFieldTests.swift in Sources */,
|
||||
B54D0C271CA56ADB004343BD /* TorrentMetaInfoTests.swift in Sources */,
|
||||
B51638951F0EEAA4009E563E /* TCPConnectionTests.swift in Sources */,
|
||||
B55317E01F02FE1500909ADF /* URLEncodeTests.swift in Sources */,
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
//
|
||||
// BitField.swift
|
||||
// BitTorrent
|
||||
//
|
||||
// Created by Ben Davis on 08/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct BitField {
|
||||
let size: Int
|
||||
var value: [Bool]
|
||||
|
||||
init(size: Int) {
|
||||
self.size = size
|
||||
self.value = Array(repeating: false, count: size)
|
||||
}
|
||||
|
||||
mutating func set(at index: Int) {
|
||||
value[index] = true
|
||||
}
|
||||
|
||||
mutating func unset(at index: Int) {
|
||||
value[index] = false
|
||||
}
|
||||
|
||||
func toData() -> Data {
|
||||
let numberOfBytes: Int
|
||||
if (size % 8) == 0 {
|
||||
numberOfBytes = size / 8
|
||||
} else {
|
||||
numberOfBytes = (size / 8) + 1
|
||||
}
|
||||
|
||||
var bytes: [UInt8] = []
|
||||
for i in 0..<numberOfBytes {
|
||||
let startIndex = i * 8
|
||||
var byte: UInt8
|
||||
if size >= startIndex + 8 {
|
||||
byte = UInt8(bits: (value[startIndex],
|
||||
value[startIndex + 1],
|
||||
value[startIndex + 2],
|
||||
value[startIndex + 3],
|
||||
value[startIndex + 4],
|
||||
value[startIndex + 5],
|
||||
value[startIndex + 6],
|
||||
value[startIndex + 7]))
|
||||
} else {
|
||||
byte = UInt8(bits: (value[startIndex],
|
||||
(size > startIndex + 1) ? value[startIndex + 1] : false,
|
||||
(size > startIndex + 2) ? value[startIndex + 2] : false,
|
||||
(size > startIndex + 3) ? value[startIndex + 3] : false,
|
||||
(size > startIndex + 4) ? value[startIndex + 4] : false,
|
||||
(size > startIndex + 5) ? value[startIndex + 5] : false,
|
||||
(size > startIndex + 6) ? value[startIndex + 6] : false,
|
||||
false))
|
||||
}
|
||||
bytes.append(byte)
|
||||
}
|
||||
|
||||
return Data(bytes: bytes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
//
|
||||
// BitFieldTests.swift
|
||||
// BitTorrentTests
|
||||
//
|
||||
// Created by Ben Davis on 08/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import BitTorrent
|
||||
|
||||
class BitFieldTests: XCTestCase {
|
||||
|
||||
func test_create() {
|
||||
let sut = BitField(size: 5)
|
||||
XCTAssertEqual(sut.size, 5)
|
||||
XCTAssertEqual(sut.value, [false, false, false, false, false])
|
||||
}
|
||||
|
||||
func test_canSetValueAtIndex() {
|
||||
var sut = BitField(size: 5)
|
||||
sut.set(at: 0)
|
||||
sut.set(at: 2)
|
||||
sut.set(at: 4)
|
||||
XCTAssertEqual(sut.value, [true, false, true, false, true])
|
||||
}
|
||||
|
||||
func test_canUnsetValueAtIndex() {
|
||||
var sut = BitField(size: 5)
|
||||
sut.set(at: 0)
|
||||
sut.set(at: 2)
|
||||
sut.set(at: 4)
|
||||
sut.unset(at: 4)
|
||||
XCTAssertEqual(sut.value, [true, false, true, false, false])
|
||||
}
|
||||
|
||||
func test_canConvertToData_bitsAlignToBytes() {
|
||||
var sut = BitField(size: 16)
|
||||
sut.set(at: 7)
|
||||
sut.set(at: 15)
|
||||
sut.set(at: 14)
|
||||
let result = sut.toData()
|
||||
XCTAssertEqual(result, Data(bytes:[1, 3]))
|
||||
}
|
||||
|
||||
func test_canConvertToData_bitsDoNotAlignToBytes() {
|
||||
var sut = BitField(size: 13)
|
||||
sut.set(at: 7)
|
||||
sut.set(at: 12)
|
||||
let result = sut.toData()
|
||||
XCTAssertEqual(result, Data(bytes:[1, 8]))
|
||||
}
|
||||
|
||||
func test_canConvertToData_lessThan8Bits() {
|
||||
var sut = BitField(size: 5)
|
||||
sut.set(at: 3)
|
||||
let result = sut.toData()
|
||||
XCTAssertEqual(result, Data(bytes:[16]))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
//
|
||||
// TorrentPeerCommunicator.swift
|
||||
// BitTorrent
|
||||
//
|
||||
// Created by Ben Davis on 07/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
class TorrentPeerCommunicator {
|
||||
|
||||
enum Message: UInt8 {
|
||||
case choke = 0
|
||||
case unchoke = 1
|
||||
case interested = 2
|
||||
case notInterested = 3
|
||||
case have = 4
|
||||
case bitfield = 5
|
||||
case request = 6
|
||||
case piece = 7
|
||||
case cancel = 8
|
||||
case port = 9
|
||||
}
|
||||
|
||||
let defaultTimeout: TimeInterval = 10
|
||||
|
||||
private let peerInfo: TorrentPeerInfo
|
||||
private let connection: TCPConnectionProtocol
|
||||
|
||||
init(peerInfo: TorrentPeerInfo, tcpConnection: TCPConnectionProtocol = TCPConnection()) {
|
||||
self.peerInfo = peerInfo
|
||||
self.connection = tcpConnection
|
||||
}
|
||||
|
||||
func connect() throws {
|
||||
try connection.connect(to: peerInfo.ip, onPort: peerInfo.port)
|
||||
}
|
||||
|
||||
func sendHandshake(for infoHash: Data, clientId: Data) {
|
||||
|
||||
let protocolString = "BitTorrent protocol"
|
||||
let protocolStringLength = UInt8(protocolString.count)
|
||||
|
||||
let payload =
|
||||
protocolStringLength.toData() + // pstrlen (Protocol string length)
|
||||
protocolString.data(using: .ascii)! + // pstr (Protocol string)
|
||||
Data(bytes: [0,0,0,0,0,0,0,0]) + // reserved (8 reserved bytes)
|
||||
infoHash + // info_hash
|
||||
clientId // peer_id of the current user
|
||||
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
private let keepAlivePayload = Data(bytes: [0, 0, 0, 0]) // 0 length message
|
||||
|
||||
func sendKeepAlive() {
|
||||
connection.write(keepAlivePayload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendChoke() {
|
||||
let payload = makePayload(forMessage: .choke)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendUnchoke() {
|
||||
let payload = makePayload(forMessage: .unchoke)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendInterested() {
|
||||
let payload = makePayload(forMessage: .interested)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendNotInterested() {
|
||||
let payload = makePayload(forMessage: .notInterested)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendHavePiece(at index: Int) {
|
||||
let data = UInt32(index).toData()
|
||||
let payload = makePayload(forMessage: .have, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendBitField(_ bitField: BitField) {
|
||||
let data = bitField.toData()
|
||||
let payload = makePayload(forMessage: .bitfield, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendRequest(fromPieceAtIndex index: Int, begin: Int, length: Int) {
|
||||
let data = UInt32(index).toData() + UInt32(begin).toData() + UInt32(length).toData()
|
||||
let payload = makePayload(forMessage: .request, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendPiece(fromPieceAtIndex index: Int, begin: Int, block: Data) {
|
||||
let data = UInt32(index).toData() + UInt32(begin).toData() + block
|
||||
let payload = makePayload(forMessage: .piece, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendCancel(forPieceAtIndex index: Int, begin: Int, length: Int) {
|
||||
let data = UInt32(index).toData() + UInt32(begin).toData() + UInt32(length).toData()
|
||||
let payload = makePayload(forMessage: .cancel, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, tag: 0)
|
||||
}
|
||||
|
||||
func sendPort(_ listenPort: UInt16) {
|
||||
// TODO: implement with DHT peer discovery
|
||||
}
|
||||
|
||||
// MARK -
|
||||
|
||||
func makePayload(forMessage message: Message, data: Data? = nil) -> Data {
|
||||
let data = data ?? Data()
|
||||
let length = UInt32(data.count + 1)
|
||||
return length.toData() + message.rawValue.toData() + data
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
//
|
||||
// TorrentPeerCommunicatorTests.swift
|
||||
// BitTorrent
|
||||
//
|
||||
// Created by Ben Davis on 07/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import BitTorrent
|
||||
|
||||
class TorrentPeerComminicatorTests: XCTestCase {
|
||||
|
||||
var tcpConnection: TCPConnectionStub!
|
||||
var sut: TorrentPeerCommunicator!
|
||||
|
||||
let ip = "127.0.0.1"
|
||||
let port: UInt16 = 123
|
||||
let peerId = "-BD0000-bxa]N#IRKqv`".data(using: .ascii)!
|
||||
|
||||
let expectedTimeout: TimeInterval = 10
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
let peer = TorrentPeerInfo(ip: ip, port: port, peerId: peerId)
|
||||
|
||||
tcpConnection = TCPConnectionStub()
|
||||
sut = TorrentPeerCommunicator(peerInfo: peer, tcpConnection: tcpConnection)
|
||||
}
|
||||
|
||||
func test_canConnect() {
|
||||
try! sut.connect()
|
||||
|
||||
XCTAssert(tcpConnection.connectCalled)
|
||||
XCTAssertEqual(tcpConnection.connectParameters?.host, ip)
|
||||
XCTAssertEqual(tcpConnection.connectParameters?.port, port)
|
||||
}
|
||||
|
||||
func test_sendHandshake() {
|
||||
|
||||
let infoHash = Data(bytes: [1, 2, 3])
|
||||
let clientId = Data(bytes: [4, 5, 6])
|
||||
|
||||
sut.sendHandshake(for: infoHash, clientId: clientId)
|
||||
|
||||
let expectedPayload =
|
||||
Data(bytes: [19]) + // pstrlen (Protocol string length)
|
||||
"BitTorrent protocol".data(using: .ascii)! + // pstr (Protocol string)
|
||||
Data(bytes: [0,0,0,0,0,0,0,0]) + // reserved (8 reserved bytes)
|
||||
infoHash + // info_hash
|
||||
clientId // peer_id of the current user
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendKeepAlive() {
|
||||
|
||||
sut.sendKeepAlive()
|
||||
|
||||
let expectedPayload = Data(bytes: [0, 0, 0, 0]) // Length prefix of 0
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendChoke() {
|
||||
sut.sendChoke()
|
||||
let expectedPayload = Data(bytes: [
|
||||
0, 0, 0, 1, // Length 1
|
||||
0 // Id 0
|
||||
])
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendUnchoke() {
|
||||
sut.sendUnchoke()
|
||||
let expectedPayload = Data(bytes: [
|
||||
0, 0, 0, 1, // Length 1
|
||||
1 // Id 1
|
||||
])
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func testSendInterested() {
|
||||
sut.sendInterested()
|
||||
let expectedPayload = Data(bytes: [
|
||||
0, 0, 0, 1, // Length 1
|
||||
2 // Id 2
|
||||
])
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func testSendNotInterested() {
|
||||
sut.sendNotInterested()
|
||||
let expectedPayload = Data(bytes: [
|
||||
0, 0, 0, 1, // Length 1
|
||||
3 // Id 3
|
||||
])
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendHave() {
|
||||
let pieceIndex = 456
|
||||
sut.sendHavePiece(at: pieceIndex)
|
||||
let expectedPayload = Data(bytes: [0, 0, 0, 5, // Length 5
|
||||
4 // Id 4
|
||||
]) + UInt32(pieceIndex).toData() // Piece index
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendBitField() {
|
||||
|
||||
// Given
|
||||
var bitField = BitField(size: 10)
|
||||
bitField.set(at: 2)
|
||||
bitField.set(at: 5)
|
||||
bitField.set(at: 9)
|
||||
|
||||
// When
|
||||
sut.sendBitField(bitField)
|
||||
|
||||
// Then
|
||||
let expectedPayload = Data(bytes: [0, 0, 0, 3, // Length 3
|
||||
5 // Id 5
|
||||
]) + bitField.toData() // Piece index
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendRequest() {
|
||||
|
||||
let index = 123
|
||||
let begin = 456
|
||||
let length = 789
|
||||
sut.sendRequest(fromPieceAtIndex: index, begin: begin, length: length)
|
||||
|
||||
let expectedPayload = Data(bytes: [0, 0, 0, 13, // Length 13
|
||||
6 // Id 6
|
||||
]) + UInt32(index).toData() + // index
|
||||
UInt32(begin).toData() + // begin
|
||||
UInt32(length).toData() // length
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendPiece() {
|
||||
let index = 123
|
||||
let begin = 456
|
||||
let block = Data(bytes: [1,2,3])
|
||||
|
||||
sut.sendPiece(fromPieceAtIndex: index, begin: begin, block: block)
|
||||
|
||||
let expectedPayload = Data(bytes: [0, 0, 0, 12, // Length 12
|
||||
7 // Id 7
|
||||
]) + UInt32(index).toData() + // index
|
||||
UInt32(begin).toData() + // begin
|
||||
block // block
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendCancel() {
|
||||
|
||||
let index = 123
|
||||
let begin = 456
|
||||
let length = 789
|
||||
sut.sendCancel(forPieceAtIndex: index, begin: begin, length: length)
|
||||
|
||||
let expectedPayload = Data(bytes: [0, 0, 0, 13, // Length 13
|
||||
8 // Id 8
|
||||
]) + UInt32(index).toData() + // index
|
||||
UInt32(begin).toData() + // begin
|
||||
UInt32(length).toData() // length
|
||||
|
||||
assertDataSent(expectedPayload)
|
||||
}
|
||||
|
||||
func test_sendPort() {
|
||||
// TODO: implement with DHT peer discovery
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
func assertDataSent(_ data: Data) {
|
||||
XCTAssert(tcpConnection.writeDataCalled)
|
||||
XCTAssertEqual(tcpConnection.writeDataParameters?.timeout, expectedTimeout)
|
||||
XCTAssertEqual(tcpConnection.writeDataParameters?.data, data)
|
||||
}
|
||||
}
|
||||
@@ -10,10 +10,10 @@ import Foundation
|
||||
|
||||
struct TorrentPeerInfo {
|
||||
let ip: String
|
||||
let port: Int
|
||||
let port: UInt16
|
||||
let peerId: Data?
|
||||
|
||||
init(ip: String, port: Int, peerId: Data?) {
|
||||
init(ip: String, port: UInt16, peerId: Data?) {
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.peerId = peerId
|
||||
@@ -28,7 +28,7 @@ struct TorrentPeerInfo {
|
||||
}
|
||||
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.port = UInt16(port)
|
||||
self.peerId = dictionary["peer id"] as? Data
|
||||
}
|
||||
|
||||
@@ -42,11 +42,10 @@ struct TorrentPeerInfo {
|
||||
let ip4 = Int(data[i*6 + 3])
|
||||
let portBytes = [data[i*6 + 5], data[i*6 + 4]]
|
||||
|
||||
let portu16 = UnsafePointer(portBytes).withMemoryRebound(to: UInt16.self, capacity: 1) {
|
||||
let port = UnsafePointer(portBytes).withMemoryRebound(to: UInt16.self, capacity: 1) {
|
||||
$0.pointee
|
||||
}
|
||||
let port = Int(portu16)
|
||||
|
||||
|
||||
let peer = TorrentPeerInfo(ip: "\(ip1).\(ip2).\(ip3).\(ip4)", port: port, peerId: nil)
|
||||
result.append(peer)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,19 @@
|
||||
import Foundation
|
||||
import CocoaAsyncSocket
|
||||
|
||||
protocol TCPConnectionProtocol {
|
||||
weak var delegate: TCPConnectionDelegate? { set get }
|
||||
|
||||
var connectedHost: String? { get }
|
||||
var connectedPort: UInt16? { get }
|
||||
|
||||
func connect(to host: String, onPort port: UInt16) throws
|
||||
func disconnect()
|
||||
|
||||
func readData(withTimeout timeout: TimeInterval, tag: Int)
|
||||
func write(_ data: Data, withTimeout timeout: TimeInterval, tag: Int)
|
||||
}
|
||||
|
||||
protocol TCPConnectionDelegate: class {
|
||||
func tcpConnection(_ sender: TCPConnection, didConnectToHost host: String, port: UInt16)
|
||||
func tcpConnection(_ sender: TCPConnection, didRead data: Data, withTag tag: Int)
|
||||
@@ -19,7 +32,7 @@ protocol TCPConnectionDelegate: class {
|
||||
/// This class is a thin wrapper around the socket library to protect against changes
|
||||
/// in its interface, and to allow me to replace CocoaAsyncSocket with a swift framework
|
||||
/// one day.
|
||||
class TCPConnection: NSObject {
|
||||
class TCPConnection: NSObject, TCPConnectionProtocol {
|
||||
|
||||
weak var delegate: TCPConnectionDelegate?
|
||||
|
||||
@@ -47,15 +60,15 @@ class TCPConnection: NSObject {
|
||||
try socket.connect(toHost: host, onPort: port)
|
||||
}
|
||||
|
||||
func readData(withTimeout timout: TimeInterval, tag: Int) {
|
||||
socket.readData(withTimeout: timout, tag: tag)
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
socket.delegate = nil
|
||||
socket.disconnect()
|
||||
}
|
||||
|
||||
func readData(withTimeout timeout: TimeInterval, tag: Int) {
|
||||
socket.readData(withTimeout: timeout, tag: tag)
|
||||
}
|
||||
|
||||
func write(_ data: Data, withTimeout timeout: TimeInterval, tag: Int) {
|
||||
socket.write(data, withTimeout: timeout, tag: tag)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
//
|
||||
// TCPConnectionStub.swift
|
||||
// BitTorrentTests
|
||||
//
|
||||
// Created by Ben Davis on 07/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@testable import BitTorrent
|
||||
|
||||
class TCPConnectionStub: TCPConnectionProtocol {
|
||||
|
||||
weak var delegate: TCPConnectionDelegate?
|
||||
|
||||
var connectedHost: String?
|
||||
var connectedPort: UInt16?
|
||||
|
||||
var connectCalled = false
|
||||
var connectParameters: (host: String, port: UInt16)?
|
||||
func connect(to host: String, onPort port: UInt16) throws {
|
||||
connectCalled = true
|
||||
connectParameters = (host, port)
|
||||
}
|
||||
|
||||
var disconnectCalled = false
|
||||
func disconnect() {
|
||||
disconnectCalled = true
|
||||
}
|
||||
|
||||
var readDataCalled = false
|
||||
var readDataParameters: (timeout: TimeInterval, tag: Int)?
|
||||
func readData(withTimeout timeout: TimeInterval, tag: Int) {
|
||||
readDataCalled = true
|
||||
readDataParameters = (timeout, tag)
|
||||
}
|
||||
|
||||
var writeDataCalled = false
|
||||
var writeDataParameters: (data: Data, timeout: TimeInterval, tag: Int)?
|
||||
func write(_ data: Data, withTimeout timeout: TimeInterval, tag: Int) {
|
||||
writeDataCalled = true
|
||||
writeDataParameters = (data, timeout, tag)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,7 +68,7 @@ class TCPConnectionTests: XCTestCase {
|
||||
// Issue with solution: Cannot call delegate methods using this protocol as parameter
|
||||
// (delegate methods are defined as taking the concrete class)
|
||||
|
||||
// For now just assert they are both nil
|
||||
// TODO: Test properly. For now just assert they are both nil
|
||||
XCTAssertNil(sut.connectedHost)
|
||||
XCTAssertNil(sut.connectedPort)
|
||||
}
|
||||
|
||||
@@ -162,10 +162,10 @@ class TorrentHTTPTrackerTests: XCTestCase {
|
||||
|
||||
let peer1Id = "peerId1-------------"
|
||||
let peer1IP = "127.0.0.1"
|
||||
let peer1Port = 15383
|
||||
let peer1Port: UInt16 = 15383
|
||||
let peer2Id = "peerId2-------------"
|
||||
let peer2IP = "127.0.0.1"
|
||||
let peer2Port = 6881
|
||||
let peer2Port: UInt16 = 6881
|
||||
|
||||
let peer1 = "d7:peer id20:\(peer1Id)2:ip9:\(peer1IP)4:porti\(peer1Port)ee"
|
||||
let peer2 = "d7:peer id20:\(peer2Id)2:ip9:\(peer2IP)4:porti\(peer2Port)ee"
|
||||
|
||||
@@ -27,10 +27,11 @@ class InternetProtocolTests: XCTestCase {
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func test_canDecodeGoogle() {
|
||||
let result = InternetProtocol.getIPAddress(of: "google.com")
|
||||
XCTAssertNotNil(result)
|
||||
}
|
||||
// Bad Test
|
||||
// func test_canDecodeGoogle() {
|
||||
// let result = InternetProtocol.getIPAddress(of: "google.com")
|
||||
// XCTAssertNotNil(result)
|
||||
// }
|
||||
|
||||
func test_canDecodeIPv4AddressFromData() {
|
||||
let data = Data(bytes: [16,2,122,105,127,0,0,1,0,0,0,0,0,0,0,0])
|
||||
|
||||
+1
-1
Submodule Submodules/BEncodeSwift updated: 7e104ab912...5818b8f70f
Reference in New Issue
Block a user