Started implementing Torrent Peer (download)
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 */; };
|
||||
B527F6271F1BBC6B001F06AF /* TorrentPeerCommunicatorStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B527F6261F1BBC6B001F06AF /* TorrentPeerCommunicatorStub.swift */; };
|
||||
B527F6291F1BCAD2001F06AF /* TorrentPeerDelegateStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B527F6281F1BCAD2001F06AF /* TorrentPeerDelegateStub.swift */; };
|
||||
B527F62B1F1BD5FA001F06AF /* TorrentPieceDownloadBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B527F62A1F1BD5FA001F06AF /* TorrentPieceDownloadBuffer.swift */; };
|
||||
B527F62D1F1BD64D001F06AF /* TorrentPieceDownloadBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B527F62C1F1BD64D001F06AF /* TorrentPieceDownloadBufferTests.swift */; };
|
||||
B527F6311F1BF30E001F06AF /* TorrentClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = B527F6301F1BF30E001F06AF /* TorrentClient.swift */; };
|
||||
B52EE3401F129CA200AC22D6 /* TorrentPeerHandshakeMessageBuffer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52EE33F1F129CA200AC22D6 /* TorrentPeerHandshakeMessageBuffer.swift */; };
|
||||
B52EE3431F129CB000AC22D6 /* TorrentPeerHandshakeMessageBufferTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52EE3421F129CB000AC22D6 /* TorrentPeerHandshakeMessageBufferTests.swift */; };
|
||||
B53553361F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53553351F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift */; };
|
||||
@@ -52,6 +57,8 @@
|
||||
B585AB841C3833450093FA41 /* BitTorrentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B585AB831C3833450093FA41 /* BitTorrentTests.swift */; };
|
||||
B59E1B281F0E6EA3007753CE /* BitTorrentTestMacros.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59E1B261F0E6E5F007753CE /* BitTorrentTestMacros.swift */; };
|
||||
B5BD7FD61F03032400621BC2 /* TorrentHTTPTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BD7FD51F03032400621BC2 /* TorrentHTTPTrackerTests.swift */; };
|
||||
B5C06F131F12CDD8005730B3 /* TorrentPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C06F121F12CDD8005730B3 /* TorrentPeer.swift */; };
|
||||
B5C06F151F12CDE1005730B3 /* TorrentPeerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C06F141F12CDE1005730B3 /* TorrentPeerTests.swift */; };
|
||||
B5E977961CAFB46B0038EBE7 /* String+URLEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E977951CAFB46B0038EBE7 /* String+URLEncode.swift */; };
|
||||
B5E9B0D81F02E6F800EF58E3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E9B0D71F02E6F800EF58E3 /* AppDelegate.swift */; };
|
||||
B5E9B0DA1F02E6F800EF58E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0D91F02E6F800EF58E3 /* Assets.xcassets */; };
|
||||
@@ -141,6 +148,11 @@
|
||||
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>"; };
|
||||
B527F6261F1BBC6B001F06AF /* TorrentPeerCommunicatorStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerCommunicatorStub.swift; sourceTree = "<group>"; };
|
||||
B527F6281F1BCAD2001F06AF /* TorrentPeerDelegateStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerDelegateStub.swift; sourceTree = "<group>"; };
|
||||
B527F62A1F1BD5FA001F06AF /* TorrentPieceDownloadBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPieceDownloadBuffer.swift; sourceTree = "<group>"; };
|
||||
B527F62C1F1BD64D001F06AF /* TorrentPieceDownloadBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPieceDownloadBufferTests.swift; sourceTree = "<group>"; };
|
||||
B527F6301F1BF30E001F06AF /* TorrentClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentClient.swift; sourceTree = "<group>"; };
|
||||
B52EE33F1F129CA200AC22D6 /* TorrentPeerHandshakeMessageBuffer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerHandshakeMessageBuffer.swift; sourceTree = "<group>"; };
|
||||
B52EE3421F129CB000AC22D6 /* TorrentPeerHandshakeMessageBufferTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerHandshakeMessageBufferTests.swift; sourceTree = "<group>"; };
|
||||
B53553351F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerCommunicator.swift; sourceTree = "<group>"; };
|
||||
@@ -179,6 +191,8 @@
|
||||
B585AB851C3833450093FA41 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B59E1B261F0E6E5F007753CE /* BitTorrentTestMacros.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BitTorrentTestMacros.swift; path = Utilities/BitTorrentTestMacros.swift; sourceTree = "<group>"; };
|
||||
B5BD7FD51F03032400621BC2 /* TorrentHTTPTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentHTTPTrackerTests.swift; sourceTree = "<group>"; };
|
||||
B5C06F121F12CDD8005730B3 /* TorrentPeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeer.swift; sourceTree = "<group>"; };
|
||||
B5C06F141F12CDE1005730B3 /* TorrentPeerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerTests.swift; sourceTree = "<group>"; };
|
||||
B5E977951CAFB46B0038EBE7 /* String+URLEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "String+URLEncode.swift"; path = "HTTP Networking/String+URLEncode.swift"; sourceTree = "<group>"; };
|
||||
B5E9B0D51F02E6F800EF58E3 /* BitTorrentExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitTorrentExample.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5E9B0D71F02E6F800EF58E3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
@@ -271,6 +285,23 @@
|
||||
path = "TCP Networking";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B527F62E1F1BDF39001F06AF /* Piece Download Buffer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B527F62A1F1BD5FA001F06AF /* TorrentPieceDownloadBuffer.swift */,
|
||||
B527F62C1F1BD64D001F06AF /* TorrentPieceDownloadBufferTests.swift */,
|
||||
);
|
||||
path = "Piece Download Buffer";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B527F62F1F1BF2EB001F06AF /* Torrent Client */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B527F6301F1BF30E001F06AF /* TorrentClient.swift */,
|
||||
);
|
||||
path = "Torrent Client";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B54D0C131CA53979004343BD /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -373,6 +404,7 @@
|
||||
children = (
|
||||
B585AB771C3833450093FA41 /* BitTorrent.h */,
|
||||
B585AB831C3833450093FA41 /* BitTorrentTests.swift */,
|
||||
B527F62F1F1BF2EB001F06AF /* Torrent Client */,
|
||||
B5F81E461F0436CC00B25C70 /* Peer */,
|
||||
B55317DA1F02FC3000909ADF /* Tracker */,
|
||||
B54D0C251CA56A25004343BD /* Models */,
|
||||
@@ -399,6 +431,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B53553351F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift */,
|
||||
B527F6261F1BBC6B001F06AF /* TorrentPeerCommunicatorStub.swift */,
|
||||
B53553381F0FEB9300A6DCBE /* TorrentPeerCommunicatorTests.swift */,
|
||||
B53553411F12432A00A6DCBE /* TorrentPeerCommunicatorReadTests.swift */,
|
||||
B53553441F1250E300A6DCBE /* TorrentPeerCommunicatorDelegateStub.swift */,
|
||||
@@ -453,6 +486,10 @@
|
||||
B5F81E461F0436CC00B25C70 /* Peer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5C06F121F12CDD8005730B3 /* TorrentPeer.swift */,
|
||||
B527F6281F1BCAD2001F06AF /* TorrentPeerDelegateStub.swift */,
|
||||
B5C06F141F12CDE1005730B3 /* TorrentPeerTests.swift */,
|
||||
B527F62E1F1BDF39001F06AF /* Piece Download Buffer */,
|
||||
B5C06F101F12C2ED005730B3 /* Communicator */,
|
||||
);
|
||||
path = Peer;
|
||||
@@ -808,11 +845,14 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5F81E491F0436D600B25C70 /* TorrentPeerInfo.swift in Sources */,
|
||||
B527F6311F1BF30E001F06AF /* TorrentClient.swift in Sources */,
|
||||
B55317DC1F02FC4D00909ADF /* TorrentHTTPTracker.swift in Sources */,
|
||||
B52EE3401F129CA200AC22D6 /* TorrentPeerHandshakeMessageBuffer.swift in Sources */,
|
||||
B5E977961CAFB46B0038EBE7 /* String+URLEncode.swift in Sources */,
|
||||
B558F4831F0A647D00438BB4 /* InternetProtocol.swift in Sources */,
|
||||
B527F62B1F1BD5FA001F06AF /* TorrentPieceDownloadBuffer.swift in Sources */,
|
||||
B54D0C7A1CA69FAD004343BD /* TorrentMetaInfo.swift in Sources */,
|
||||
B5C06F131F12CDD8005730B3 /* TorrentPeer.swift in Sources */,
|
||||
B5F81E4B1F04399800B25C70 /* TorrentTrackerResponse.swift in Sources */,
|
||||
B54D0C7B1CA69FD8004343BD /* Data+sha1.swift in Sources */,
|
||||
B53553361F0FEB8800A6DCBE /* TorrentPeerCommunicator.swift in Sources */,
|
||||
@@ -831,6 +871,7 @@
|
||||
files = (
|
||||
B535533A1F0FEB9700A6DCBE /* TorrentPeerCommunicatorTests.swift in Sources */,
|
||||
B51638971F0EEC2B009E563E /* GCDAsyncSocketStub.swift in Sources */,
|
||||
B5C06F151F12CDE1005730B3 /* TorrentPeerTests.swift in Sources */,
|
||||
B51D6C091F0C180600E1E3AB /* TorrentUDPTrackerTests.swift in Sources */,
|
||||
B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */,
|
||||
B53553451F1250E300A6DCBE /* TorrentPeerCommunicatorDelegateStub.swift in Sources */,
|
||||
@@ -841,11 +882,14 @@
|
||||
B535533C1F0FEE6300A6DCBE /* TCPConnectionStub.swift in Sources */,
|
||||
B52EE3431F129CB000AC22D6 /* TorrentPeerHandshakeMessageBufferTests.swift in Sources */,
|
||||
B535534C1F125DB500A6DCBE /* TorrentPeerMessageBufferTests.swift in Sources */,
|
||||
B527F6271F1BBC6B001F06AF /* TorrentPeerCommunicatorStub.swift in Sources */,
|
||||
B56A8A071C83539300426AC8 /* TestHelpers.swift in Sources */,
|
||||
B585AB841C3833450093FA41 /* BitTorrentTests.swift in Sources */,
|
||||
B53553401F112BDB00A6DCBE /* BitFieldTests.swift in Sources */,
|
||||
B54D0C271CA56ADB004343BD /* TorrentMetaInfoTests.swift in Sources */,
|
||||
B527F62D1F1BD64D001F06AF /* TorrentPieceDownloadBufferTests.swift in Sources */,
|
||||
B51638951F0EEAA4009E563E /* TCPConnectionTests.swift in Sources */,
|
||||
B527F6291F1BCAD2001F06AF /* TorrentPeerDelegateStub.swift in Sources */,
|
||||
B55317E01F02FE1500909ADF /* URLEncodeTests.swift in Sources */,
|
||||
B558F4851F0A73D000438BB4 /* InternetProtocolTests.swift in Sources */,
|
||||
B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */,
|
||||
|
||||
@@ -102,7 +102,7 @@ class TorrentInfoDictionary {
|
||||
let pieceLength : Int
|
||||
let isPrivate : Bool
|
||||
let files: [TorrentFileInfo]
|
||||
let pieces : [Data]?
|
||||
let pieces : [Data]
|
||||
let length: Int
|
||||
|
||||
init?(_ dictionary: [String : AnyObject]) {
|
||||
|
||||
@@ -52,3 +52,10 @@ struct TorrentPeerInfo {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
extension TorrentPeerInfo: Equatable {
|
||||
static func ==(_ lhs: TorrentPeerInfo, _ rhs: TorrentPeerInfo) -> Bool {
|
||||
let peerIdsMatch = (lhs.peerId == nil || rhs.peerId == nil || lhs.peerId == rhs.peerId)
|
||||
return (lhs.ip == rhs.ip && lhs.port == rhs.port && peerIdsMatch)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ protocol TorrentPeerCommunicatorDelegate: class {
|
||||
/// Responsible for sending and recieving messages in the Peer Wire Protocol
|
||||
class TorrentPeerCommunicator {
|
||||
|
||||
var enableLogging = false
|
||||
|
||||
enum Message: UInt8 {
|
||||
case choke = 0
|
||||
case unchoke = 1
|
||||
@@ -42,6 +44,31 @@ class TorrentPeerCommunicator {
|
||||
case piece = 7
|
||||
case cancel = 8
|
||||
case port = 9
|
||||
|
||||
var stringValue: String {
|
||||
switch self {
|
||||
case .choke:
|
||||
return "choke"
|
||||
case .unchoke:
|
||||
return "unchoke"
|
||||
case .interested:
|
||||
return "interested"
|
||||
case .notInterested:
|
||||
return "notInterested"
|
||||
case .have:
|
||||
return "have"
|
||||
case .bitfield:
|
||||
return "bitfield"
|
||||
case .request:
|
||||
return "request"
|
||||
case .piece:
|
||||
return "piece"
|
||||
case .cancel:
|
||||
return "cancel"
|
||||
case .port:
|
||||
return "port"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let defaultTimeout: TimeInterval = 10
|
||||
@@ -51,13 +78,16 @@ class TorrentPeerCommunicator {
|
||||
private let peerInfo: TorrentPeerInfo
|
||||
private let connection: TCPConnectionProtocol
|
||||
|
||||
var handshakeReceived = false
|
||||
fileprivate let infoHash: Data
|
||||
|
||||
fileprivate var handshakeReceived = false
|
||||
fileprivate let handshakeMessageBuffer: TorrentPeerHandshakeMessageBuffer
|
||||
fileprivate let messageBuffer: TorrentPeerMessageBuffer
|
||||
|
||||
init(peerInfo: TorrentPeerInfo, infoHash: Data, tcpConnection: TCPConnectionProtocol = TCPConnection()) {
|
||||
self.peerInfo = peerInfo
|
||||
self.connection = tcpConnection
|
||||
self.infoHash = infoHash
|
||||
self.handshakeMessageBuffer = TorrentPeerHandshakeMessageBuffer(infoHash: infoHash, peerId: peerInfo.peerId)
|
||||
self.messageBuffer = TorrentPeerMessageBuffer()
|
||||
|
||||
@@ -71,13 +101,12 @@ class TorrentPeerCommunicator {
|
||||
func connect() throws {
|
||||
try connection.connect(to: peerInfo.ip, onPort: peerInfo.port)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Writing messages
|
||||
|
||||
extension TorrentPeerCommunicator {
|
||||
|
||||
func sendHandshake(for infoHash: Data, clientId: Data, _ completion: (()->Void)? = nil) {
|
||||
// MARK: - Writing messages
|
||||
|
||||
func sendHandshake(for clientId: Data, _ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send handshake") }
|
||||
|
||||
let protocolString = "BitTorrent protocol"
|
||||
let protocolStringLength = UInt8(protocolString.count)
|
||||
@@ -93,55 +122,85 @@ extension TorrentPeerCommunicator {
|
||||
}
|
||||
|
||||
func sendKeepAlive(_ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("sendKeepAlive") }
|
||||
|
||||
let keepAlivePayload = Data(bytes: [0, 0, 0, 0]) // 0 length message
|
||||
connection.write(keepAlivePayload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendChoke(_ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send choke") }
|
||||
|
||||
let payload = makePayload(forMessage: .choke)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendUnchoke(_ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send Unchoke") }
|
||||
|
||||
let payload = makePayload(forMessage: .unchoke)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendInterested(_ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send Interested") }
|
||||
|
||||
let payload = makePayload(forMessage: .interested)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendNotInterested(_ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send not interested") }
|
||||
|
||||
let payload = makePayload(forMessage: .notInterested)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendHavePiece(at index: Int, _ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send have piece") }
|
||||
|
||||
let data = UInt32(index).toData()
|
||||
let payload = makePayload(forMessage: .have, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendBitField(_ bitField: BitField, _ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send Bit Field") }
|
||||
|
||||
let data = bitField.toData()
|
||||
let payload = makePayload(forMessage: .bitfield, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendRequest(fromPieceAtIndex index: Int, begin: Int, length: Int, _ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send Request for piece at index: \(index) begin: \(begin)") }
|
||||
|
||||
let data = UInt32(index).toData() + UInt32(begin).toData() + UInt32(length).toData()
|
||||
let payload = makePayload(forMessage: .request, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendPiece(fromPieceAtIndex index: Int, begin: Int, block: Data, _ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send Piece index: \(index) begin: \(begin)") }
|
||||
|
||||
let data = UInt32(index).toData() + UInt32(begin).toData() + block
|
||||
let payload = makePayload(forMessage: .piece, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
}
|
||||
|
||||
func sendCancel(forPieceAtIndex index: Int, begin: Int, length: Int, _ completion: (()->Void)? = nil) {
|
||||
|
||||
if enableLogging { print("Send Cancel for piece at index: \(index) begin: \(begin)") }
|
||||
|
||||
let data = UInt32(index).toData() + UInt32(begin).toData() + UInt32(length).toData()
|
||||
let payload = makePayload(forMessage: .cancel, data: data)
|
||||
connection.write(payload, withTimeout: defaultTimeout, completion: completion)
|
||||
@@ -211,7 +270,6 @@ extension TorrentPeerCommunicator: TorrentPeerHandshakeDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test can send handshake + message
|
||||
extension TorrentPeerCommunicator: TorrentPeerMessageBufferDelegate {
|
||||
|
||||
func peerMessageBuffer(_ sender: TorrentPeerMessageBuffer, gotMessage data: Data) {
|
||||
@@ -226,6 +284,8 @@ extension TorrentPeerCommunicator: TorrentPeerMessageBufferDelegate {
|
||||
return
|
||||
}
|
||||
|
||||
if enableLogging { print("Got Message from peer \(message.stringValue)") }
|
||||
|
||||
switch message {
|
||||
|
||||
case .choke:
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
//
|
||||
// TorrentPeerCommunicatorStub.swift
|
||||
// BitTorrentTests
|
||||
//
|
||||
// Created by Ben Davis on 16/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import BitTorrent
|
||||
|
||||
class TorrentPeerCommunicatorStub: TorrentPeerCommunicator {
|
||||
|
||||
var connectCalled = false
|
||||
override func connect() throws {
|
||||
connectCalled = true
|
||||
}
|
||||
|
||||
var sendHandshakeCalled = false
|
||||
var sendHandshakeParameters: (clientId: Data, completion: (()->Void)?)?
|
||||
override func sendHandshake(for clientId: Data, _ completion: (() -> Void)?) {
|
||||
sendHandshakeCalled = true
|
||||
sendHandshakeParameters = (clientId, completion)
|
||||
}
|
||||
|
||||
var sendBitFieldCalled = false
|
||||
var sendBitFieldParameters: (bitField: BitField, completion: (()->Void)?)?
|
||||
override func sendBitField(_ bitField: BitField, _ completion: (() -> Void)?) {
|
||||
sendBitFieldCalled = true
|
||||
sendBitFieldParameters = (bitField, completion)
|
||||
}
|
||||
|
||||
var sendInterestedCalled = false
|
||||
var sendInterestedParameter: ((()->Void)?)?
|
||||
override func sendInterested(_ completion: (() -> Void)?) {
|
||||
sendInterestedCalled = true
|
||||
sendInterestedParameter = completion
|
||||
}
|
||||
|
||||
var sendRequestCalled = false
|
||||
var sendRequestParameters: [(index: Int, begin: Int, length: Int, completion:(()->Void)?)] = []
|
||||
override func sendRequest(fromPieceAtIndex index: Int, begin: Int, length: Int, _ completion: (() -> Void)?) {
|
||||
sendRequestCalled = true
|
||||
sendRequestParameters.append((index, begin, length, completion))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -15,6 +15,8 @@ class TorrentPeerComminicatorTests: XCTestCase {
|
||||
var delegate: TorrentPeerCommunicatorDelegateStub!
|
||||
var sut: TorrentPeerCommunicator!
|
||||
|
||||
// MARK: - Models and example data
|
||||
|
||||
let ip = "127.0.0.1"
|
||||
let port: UInt16 = 123
|
||||
let peerId = "-BD0000-bxa]N#IRKqv`".data(using: .ascii)!
|
||||
@@ -94,6 +96,8 @@ class TorrentPeerComminicatorTests: XCTestCase {
|
||||
UInt32(length).toData() // length
|
||||
}
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
@@ -107,6 +111,8 @@ class TorrentPeerComminicatorTests: XCTestCase {
|
||||
sut.delegate = delegate
|
||||
}
|
||||
|
||||
// Mark - Tests
|
||||
|
||||
func test_canConnect() {
|
||||
try! sut.connect()
|
||||
|
||||
@@ -128,7 +134,7 @@ class TorrentPeerComminicatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_sendHandshake() {
|
||||
sut.sendHandshake(for: infoHash, clientId: peerId)
|
||||
sut.sendHandshake(for: peerId)
|
||||
assertDataSent(handshakePayload)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
//
|
||||
// PieceDownloadBuffer.swift
|
||||
// BitTorrent
|
||||
//
|
||||
// Created by Ben Davis on 16/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TorrentBlockRequest: Equatable {
|
||||
var piece: Int
|
||||
var begin: Int
|
||||
var length: Int
|
||||
|
||||
static func ==(_ lhs: TorrentBlockRequest, _ rhs: TorrentBlockRequest) -> Bool {
|
||||
return (lhs.piece == rhs.piece && lhs.begin == rhs.begin && lhs.length == rhs.length)
|
||||
}
|
||||
}
|
||||
|
||||
class TorrentPieceDownloadBuffer {
|
||||
|
||||
static let blockSize = 16_384
|
||||
|
||||
let index: Int
|
||||
let size: Int
|
||||
|
||||
var isComplete: Bool {
|
||||
return unusedBlockRequests.count == 0 && pendingRequests.count == 0
|
||||
}
|
||||
|
||||
var piece: Data? {
|
||||
return isComplete ? data : nil
|
||||
}
|
||||
|
||||
private var data: Data
|
||||
private var unusedBlockRequests: [TorrentBlockRequest]
|
||||
private var pendingRequests: [TorrentBlockRequest] = []
|
||||
|
||||
init(index: Int, size: Int) {
|
||||
self.index = index
|
||||
self.size = size
|
||||
self.data = Data(repeating: 0, count: size)
|
||||
|
||||
let blockSize = TorrentPieceDownloadBuffer.blockSize
|
||||
|
||||
var blockRequests: [TorrentBlockRequest] = []
|
||||
for i in 0..<size where i % blockSize == 0 {
|
||||
|
||||
// Last block should be the remaining bytes
|
||||
let length: Int = ((i + blockSize) <= size) ? blockSize : (size - i)
|
||||
|
||||
blockRequests.append(TorrentBlockRequest(piece: index, begin: i, length: length))
|
||||
}
|
||||
|
||||
self.unusedBlockRequests = blockRequests
|
||||
}
|
||||
|
||||
func nextDownloadBlock() -> TorrentBlockRequest? {
|
||||
guard unusedBlockRequests.count > 0 else { return nil }
|
||||
let result = unusedBlockRequests.removeLast()
|
||||
pendingRequests.append(result)
|
||||
return result
|
||||
}
|
||||
|
||||
func gotBlock(_ blockData: Data, begin: Int) {
|
||||
|
||||
let request = TorrentBlockRequest(piece: index, begin: begin, length: blockData.count)
|
||||
guard let pendingIndex = pendingRequests.index(of: request) else { return }
|
||||
|
||||
let range = begin ..< (begin+blockData.count)
|
||||
data.replaceSubrange(range, with: blockData)
|
||||
|
||||
pendingRequests.remove(at: pendingIndex)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// TorrentPieceDownloadBufferTests.swift
|
||||
// BitTorrentTests
|
||||
//
|
||||
// Created by Ben Davis on 16/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import BitTorrent
|
||||
|
||||
class TorrentPieceDownloadBufferTests: XCTestCase {
|
||||
|
||||
let index = 123
|
||||
let blockSize = TorrentPieceDownloadBuffer.blockSize
|
||||
|
||||
func test_creation() {
|
||||
let size = 234
|
||||
let sut = TorrentPieceDownloadBuffer(index: index, size: size)
|
||||
XCTAssertEqual(sut.index, index)
|
||||
XCTAssertEqual(sut.size, size)
|
||||
}
|
||||
|
||||
func test_splitsPieceIntoBlockRequests() {
|
||||
let size: Int = Int(Double(blockSize)*2.5)
|
||||
let sut = TorrentPieceDownloadBuffer(index: index, size: size)
|
||||
|
||||
let block1 = sut.nextDownloadBlock()
|
||||
let block2 = sut.nextDownloadBlock()
|
||||
let block3 = sut.nextDownloadBlock()
|
||||
let block4 = sut.nextDownloadBlock()
|
||||
|
||||
XCTAssertNil(block4)
|
||||
|
||||
let sortedBlocks = [block1!, block2!, block3!].sorted(by: { $0.begin < $1.begin })
|
||||
XCTAssertEqual(sortedBlocks, [
|
||||
TorrentBlockRequest(piece: index, begin: 0, length: blockSize),
|
||||
TorrentBlockRequest(piece: index, begin: blockSize, length: blockSize),
|
||||
TorrentBlockRequest(piece: index, begin: blockSize*2, length: Int(Double(blockSize)*0.5)),
|
||||
])
|
||||
}
|
||||
|
||||
func test_isComplete() {
|
||||
let size: Int = Int(Double(blockSize)*2.5)
|
||||
let sut = TorrentPieceDownloadBuffer(index: index, size: size)
|
||||
|
||||
XCTAssertFalse(sut.isComplete)
|
||||
|
||||
_ = sut.nextDownloadBlock()
|
||||
_ = sut.nextDownloadBlock()
|
||||
_ = sut.nextDownloadBlock()
|
||||
|
||||
XCTAssertFalse(sut.isComplete)
|
||||
|
||||
let data1 = Data(repeating: 0, count: blockSize)
|
||||
let data2 = Data(repeating: 0, count: blockSize)
|
||||
let data3 = Data(repeating: 0, count: Int(Double(blockSize)*0.5))
|
||||
|
||||
sut.gotBlock(data1, begin: 0)
|
||||
XCTAssertFalse(sut.isComplete)
|
||||
|
||||
sut.gotBlock(data2, begin: blockSize)
|
||||
XCTAssertFalse(sut.isComplete)
|
||||
|
||||
sut.gotBlock(data3, begin: blockSize*2)
|
||||
XCTAssertTrue(sut.isComplete)
|
||||
}
|
||||
|
||||
func test_resultingData() {
|
||||
let size: Int = Int(Double(blockSize)*2.5)
|
||||
let sut = TorrentPieceDownloadBuffer(index: index, size: size)
|
||||
|
||||
XCTAssertNil(sut.piece)
|
||||
|
||||
_ = sut.nextDownloadBlock()
|
||||
_ = sut.nextDownloadBlock()
|
||||
_ = sut.nextDownloadBlock()
|
||||
|
||||
XCTAssertNil(sut.piece)
|
||||
|
||||
let data1 = Data(repeating: 1, count: blockSize)
|
||||
let data2 = Data(repeating: 2, count: blockSize)
|
||||
let data3 = Data(repeating: 3, count: Int(Double(blockSize)*0.5))
|
||||
let complete = data1 + data2 + data3
|
||||
|
||||
sut.gotBlock(data1, begin: 0)
|
||||
XCTAssertNil(sut.piece)
|
||||
|
||||
sut.gotBlock(data2, begin: blockSize)
|
||||
XCTAssertNil(sut.piece)
|
||||
|
||||
sut.gotBlock(data3, begin: blockSize*2)
|
||||
XCTAssertEqual(sut.piece, complete)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
//
|
||||
// TorrentPeer.swift
|
||||
// BitTorrent
|
||||
//
|
||||
// Created by Ben Davis on 09/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol TorrentPeerDelegate: class {
|
||||
func peerCompletedHandshake(_ sender: TorrentPeer)
|
||||
func peerLost(_ sender: TorrentPeer)
|
||||
func peer(_ sender: TorrentPeer, gotPieceAtIndex index: Int, piece: Data)
|
||||
func peer(_ sender: TorrentPeer, failedToGetPieceAtIndex index: Int)
|
||||
}
|
||||
|
||||
class TorrentPeer {
|
||||
|
||||
var enableLogging = false {
|
||||
didSet {
|
||||
communicator.enableLogging = enableLogging
|
||||
}
|
||||
}
|
||||
|
||||
static let maximumNumberOfPendingBlockRequests = 20
|
||||
|
||||
weak var delegate: TorrentPeerDelegate?
|
||||
|
||||
let peerInfo: TorrentPeerInfo
|
||||
private let communicator: TorrentPeerCommunicator
|
||||
|
||||
// Peer state
|
||||
var peerChoked: Bool { return _peerChoked }
|
||||
var peerInterested: Bool { return _peerInterested }
|
||||
var amChokedToPeer: Bool { return _amChokedToPeer }
|
||||
var amInterestedInPeer: Bool { return _amInterestedInPeer }
|
||||
var currentProgress: BitField { return _currentProgress }
|
||||
|
||||
// Private state
|
||||
private var _peerChoked: Bool = true
|
||||
private var _peerInterested: Bool = false
|
||||
private var _amChokedToPeer: Bool = true
|
||||
private var _amInterestedInPeer: Bool = false
|
||||
private var _currentProgress: BitField
|
||||
|
||||
private var downloadPieceRequests: [Int: TorrentPieceDownloadBuffer] = [:]
|
||||
private var numberOfPendingRequests = 0
|
||||
|
||||
init(peerInfo: TorrentPeerInfo, bitFieldSize: Int, communicator: TorrentPeerCommunicator) {
|
||||
self.peerInfo = peerInfo
|
||||
self.communicator = communicator
|
||||
self._currentProgress = BitField(size: bitFieldSize)
|
||||
communicator.delegate = self
|
||||
}
|
||||
|
||||
convenience init(peerInfo: TorrentPeerInfo, infoHash: Data, bitFieldSize: Int) {
|
||||
let communicator = TorrentPeerCommunicator(peerInfo: peerInfo, infoHash: infoHash)
|
||||
self.init(peerInfo: peerInfo, bitFieldSize: bitFieldSize, communicator: communicator)
|
||||
}
|
||||
|
||||
private var handshakeData: (clientId: Data, bitField: BitField)?
|
||||
|
||||
func connect(withHandshakeData handshakeData:(clientId: Data, bitField: BitField)) throws {
|
||||
self.handshakeData = handshakeData
|
||||
try communicator.connect()
|
||||
}
|
||||
|
||||
func downloadPiece(atIndex index: Int, size: Int) {
|
||||
|
||||
let downloadBuffer = TorrentPieceDownloadBuffer(index: index, size: size)
|
||||
downloadPieceRequests[index] = downloadBuffer
|
||||
|
||||
if !amInterestedInPeer {
|
||||
_amInterestedInPeer = true
|
||||
communicator.sendInterested()
|
||||
}
|
||||
|
||||
if !peerChoked {
|
||||
requestNextBlock()
|
||||
}
|
||||
}
|
||||
|
||||
private func requestNextBlock() {
|
||||
if numberOfPendingRequests < TorrentPeer.maximumNumberOfPendingBlockRequests {
|
||||
|
||||
guard let pieceRequest = downloadPieceRequests.values.first else { return }
|
||||
guard let blockRequest = pieceRequest.nextDownloadBlock() else { return }
|
||||
|
||||
numberOfPendingRequests += 1
|
||||
|
||||
communicator.sendRequest(fromPieceAtIndex: blockRequest.piece,
|
||||
begin: blockRequest.begin,
|
||||
length: blockRequest.length)
|
||||
requestNextBlock()
|
||||
}
|
||||
}
|
||||
|
||||
private func killAllDownloads() {
|
||||
for downloadPieceRequest in downloadPieceRequests {
|
||||
delegate?.peer(self, failedToGetPieceAtIndex: downloadPieceRequest.value.index)
|
||||
}
|
||||
downloadPieceRequests.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
extension TorrentPeer: TorrentPeerCommunicatorDelegate {
|
||||
|
||||
func peerConnected(_ sender: TorrentPeerCommunicator) {
|
||||
|
||||
if enableLogging { print("Peer socket connected (\(peerInfo.ip):\(peerInfo.port)") }
|
||||
|
||||
guard let (clientId, bitField) = handshakeData else { return }
|
||||
|
||||
communicator.sendHandshake(for: clientId) { [weak self] in
|
||||
self?.communicator.sendBitField(bitField)
|
||||
}
|
||||
}
|
||||
|
||||
func peerLost(_ sender: TorrentPeerCommunicator) {
|
||||
killAllDownloads()
|
||||
delegate?.peerLost(self)
|
||||
}
|
||||
|
||||
func peerSentHandshake(_ sender: TorrentPeerCommunicator, sentHandshakeWithPeerId peerId: Data, onDHT: Bool) {
|
||||
delegate?.peerCompletedHandshake(self)
|
||||
}
|
||||
|
||||
func peerSentKeepAlive(_ sender: TorrentPeerCommunicator) {
|
||||
|
||||
}
|
||||
|
||||
func peerBecameChoked(_ sender: TorrentPeerCommunicator) {
|
||||
_peerChoked = true
|
||||
killAllDownloads()
|
||||
}
|
||||
|
||||
func peerBecameUnchoked(_ sender: TorrentPeerCommunicator) {
|
||||
_peerChoked = false
|
||||
requestNextBlock()
|
||||
}
|
||||
|
||||
func peerBecameInterested(_ sender: TorrentPeerCommunicator) {
|
||||
_peerInterested = true
|
||||
}
|
||||
|
||||
func peerBecameUninterested(_ sender: TorrentPeerCommunicator) {
|
||||
_peerInterested = false
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, hasPiece piece: Int) {
|
||||
_currentProgress.set(at: piece)
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, hasBitField bitField: BitField) {
|
||||
_currentProgress = bitField
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, requestedPiece index: Int, begin: Int, length: Int) {
|
||||
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, sentPiece index: Int, begin: Int, block: Data) {
|
||||
guard let downloadPieceBuffer = downloadPieceRequests[index] else { return }
|
||||
numberOfPendingRequests -= 1
|
||||
downloadPieceBuffer.gotBlock(block, begin: begin)
|
||||
if downloadPieceBuffer.isComplete, let piece = downloadPieceBuffer.piece {
|
||||
delegate?.peer(self, gotPieceAtIndex: index, piece: piece)
|
||||
}
|
||||
requestNextBlock()
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, cancelledRequestedPiece index: Int, begin: Int, length: Int) {
|
||||
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, onDHTPort port: Int) {
|
||||
|
||||
}
|
||||
|
||||
func peerSentMalformedMessage(_ sender: TorrentPeerCommunicator) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// TorrentPeerDelegateStub.swift
|
||||
// BitTorrentTests
|
||||
//
|
||||
// Created by Ben Davis on 16/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
@testable import BitTorrent
|
||||
|
||||
class TorrentPeerDelegateStub: TorrentPeerDelegate {
|
||||
|
||||
var peerCompletedHandshakeCalled = false
|
||||
var peerCompletedHandshakeParameter: TorrentPeer?
|
||||
func peerCompletedHandshake(_ sender: TorrentPeer) {
|
||||
peerCompletedHandshakeCalled = true
|
||||
peerCompletedHandshakeParameter = sender
|
||||
}
|
||||
|
||||
var peerLostCalled = false
|
||||
var peerLostParameter: TorrentPeer?
|
||||
func peerLost(_ sender: TorrentPeer) {
|
||||
peerLostCalled = true
|
||||
peerLostParameter = sender
|
||||
}
|
||||
|
||||
var failedToGetPieceAtIndexCalled = false
|
||||
var failedToGetPieceAtIndexParameters: (sender: TorrentPeer, index: Int)?
|
||||
func peer(_ sender: TorrentPeer, failedToGetPieceAtIndex index: Int) {
|
||||
failedToGetPieceAtIndexCalled = true
|
||||
failedToGetPieceAtIndexParameters = (sender, index)
|
||||
}
|
||||
|
||||
var gotPieceAtIndexCalled = false
|
||||
var gotPieceAtIndexParameters: (sender: TorrentPeer, index: Int, piece: Data)?
|
||||
func peer(_ sender: TorrentPeer, gotPieceAtIndex index: Int, piece: Data) {
|
||||
gotPieceAtIndexCalled = true
|
||||
gotPieceAtIndexParameters = (sender, index, piece)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
//
|
||||
// TorrentPeerTests.swift
|
||||
// BitTorrentTests
|
||||
//
|
||||
// Created by Ben Davis on 09/07/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import BitTorrent
|
||||
|
||||
class TorrentPeerTests: XCTestCase {
|
||||
|
||||
let ip = "127.0.0.1"
|
||||
let port: UInt16 = 123
|
||||
let peerId = Data(repeating: 1, count: 20)
|
||||
let clientId = Data(repeating: 2, count: 20)
|
||||
let infoHash = Data(repeating: 3, count: 20)
|
||||
|
||||
let pieceSize = Int(Double(TorrentPieceDownloadBuffer.blockSize)*2.5)
|
||||
let pieceIndex = 123
|
||||
|
||||
let bitField: BitField = {
|
||||
var bitField = BitField(size: 10)
|
||||
bitField.set(at: 2)
|
||||
bitField.set(at: 5)
|
||||
bitField.set(at: 9)
|
||||
return bitField
|
||||
}()
|
||||
|
||||
var delegate: TorrentPeerDelegateStub!
|
||||
var communicator: TorrentPeerCommunicatorStub!
|
||||
var peerInfo: TorrentPeerInfo!
|
||||
var sut: TorrentPeer!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
peerInfo = TorrentPeerInfo(ip: ip, port: port, peerId: peerId)
|
||||
communicator = TorrentPeerCommunicatorStub(peerInfo: peerInfo,
|
||||
infoHash: infoHash,
|
||||
tcpConnection: TCPConnectionStub())
|
||||
delegate = TorrentPeerDelegateStub()
|
||||
sut = TorrentPeer(peerInfo: peerInfo, bitFieldSize: 10, communicator: communicator)
|
||||
sut.delegate = delegate
|
||||
}
|
||||
|
||||
func test_creation() {
|
||||
XCTAssertEqual(sut.peerInfo, peerInfo)
|
||||
XCTAssertTrue(sut.peerChoked)
|
||||
XCTAssertFalse(sut.peerInterested)
|
||||
XCTAssertTrue(sut.amChokedToPeer)
|
||||
XCTAssertFalse(sut.amInterestedInPeer)
|
||||
}
|
||||
|
||||
// MARK: - Connection + handshake
|
||||
|
||||
func test_canConnectToPeer() {
|
||||
try! sut.connect(withHandshakeData: (clientId, bitField))
|
||||
XCTAssert(communicator.connectCalled)
|
||||
}
|
||||
|
||||
func test_handshakeSentOnConnect() {
|
||||
try! sut.connect(withHandshakeData: (clientId, bitField))
|
||||
|
||||
communicator.delegate?.peerConnected(communicator)
|
||||
|
||||
XCTAssert(communicator.sendHandshakeCalled)
|
||||
XCTAssertEqual(communicator.sendHandshakeParameters?.clientId, clientId)
|
||||
}
|
||||
|
||||
func test_bitFieldSentAfterHandshake() {
|
||||
|
||||
// Given
|
||||
try! sut.connect(withHandshakeData: (clientId, bitField))
|
||||
communicator.delegate?.peerConnected(communicator)
|
||||
guard let handshakeCompletion = communicator.sendHandshakeParameters?.completion else {
|
||||
XCTFail("Cannot notify sut of handshake completion")
|
||||
return
|
||||
}
|
||||
|
||||
// When
|
||||
handshakeCompletion()
|
||||
|
||||
// Then
|
||||
XCTAssert(communicator.sendBitFieldCalled)
|
||||
XCTAssertEqual(communicator.sendBitFieldParameters?.bitField, bitField)
|
||||
}
|
||||
|
||||
func test_delegateNotifiedAfterHandshakeMade() {
|
||||
try! sut.connect(withHandshakeData: (clientId, bitField))
|
||||
communicator.delegate?.peerConnected(communicator)
|
||||
communicator.delegate?.peerSentHandshake(communicator, sentHandshakeWithPeerId: peerId, onDHT: false)
|
||||
|
||||
XCTAssert(delegate.peerCompletedHandshakeCalled)
|
||||
XCTAssert(delegate.peerCompletedHandshakeParameter === sut)
|
||||
}
|
||||
|
||||
// MARK: - Tracking peer status
|
||||
|
||||
func test_bitFieldRecorded() {
|
||||
var bitField = BitField(size: 10)
|
||||
bitField.set(at: 0)
|
||||
communicator.delegate?.peer(communicator, hasBitField: bitField)
|
||||
XCTAssertEqual(sut.currentProgress, bitField)
|
||||
}
|
||||
|
||||
func test_bitFieldUpdatedOnHave() {
|
||||
var bitField = BitField(size: 10)
|
||||
bitField.set(at: 0)
|
||||
bitField.set(at: 3)
|
||||
|
||||
communicator.delegate?.peer(communicator, hasPiece: 0)
|
||||
communicator.delegate?.peer(communicator, hasPiece: 3)
|
||||
XCTAssertEqual(sut.currentProgress, bitField)
|
||||
}
|
||||
|
||||
func test_stateUpdatedOnPeerUnchoked() {
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
XCTAssertFalse(sut.peerChoked)
|
||||
}
|
||||
|
||||
func test_stateUpdatedOnPeerChoked() {
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
communicator.delegate?.peerBecameChoked(communicator)
|
||||
XCTAssertTrue(sut.peerChoked)
|
||||
}
|
||||
|
||||
func test_stateUpdatedOnPeerInterested() {
|
||||
communicator.delegate?.peerBecameInterested(communicator)
|
||||
XCTAssertTrue(sut.peerInterested)
|
||||
}
|
||||
|
||||
func test_stateUpdatedOnPeerUninterested() {
|
||||
communicator.delegate?.peerBecameInterested(communicator)
|
||||
communicator.delegate?.peerBecameUninterested(communicator)
|
||||
XCTAssertFalse(sut.peerInterested)
|
||||
}
|
||||
|
||||
func test_interestedSentOnDownloadPieceRequest() {
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
XCTAssert(communicator.sendInterestedCalled)
|
||||
XCTAssertTrue(sut.amInterestedInPeer)
|
||||
}
|
||||
|
||||
func test_interestedNotSentIfAlreadyIntereseted() {
|
||||
// Given
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
communicator.sendInterestedCalled = false
|
||||
|
||||
// When
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
|
||||
// Then
|
||||
XCTAssertFalse(communicator.sendInterestedCalled)
|
||||
}
|
||||
|
||||
// MARK: - Piece download requests
|
||||
|
||||
func test_requestNotMadeIfPeerIsChoked() {
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
XCTAssertFalse(communicator.sendRequestCalled)
|
||||
}
|
||||
|
||||
func test_requestsMadeImmediatelyIfPeerIsUnchoked() {
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
XCTAssertTrue(communicator.sendRequestCalled)
|
||||
}
|
||||
|
||||
func test_sendPieceRequestOnUnchoke() {
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
XCTAssertTrue(communicator.sendRequestCalled)
|
||||
}
|
||||
|
||||
func test_correctBlockRequestsSent() {
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
|
||||
let blockSize = TorrentPieceDownloadBuffer.blockSize
|
||||
|
||||
let requests = communicator.sendRequestParameters.sorted(by: { $0.begin < $1.begin }).map {
|
||||
TorrentBlockRequest(piece: $0.index, begin: $0.begin, length: $0.length)
|
||||
}
|
||||
|
||||
let expected = [
|
||||
TorrentBlockRequest(piece: pieceIndex, begin: 0, length: blockSize),
|
||||
TorrentBlockRequest(piece: pieceIndex, begin: blockSize, length: blockSize),
|
||||
TorrentBlockRequest(piece: pieceIndex, begin: blockSize*2, length: Int(Double(blockSize)*0.5)),
|
||||
]
|
||||
|
||||
XCTAssertEqual(requests, expected)
|
||||
}
|
||||
|
||||
func test_doesNotDownloadMoreThanMaximumNumberOfRequests() {
|
||||
|
||||
let largePieceSize = TorrentPieceDownloadBuffer.blockSize * (TorrentPeer.maximumNumberOfPendingBlockRequests + 1)
|
||||
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: largePieceSize)
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
|
||||
XCTAssertEqual(communicator.sendRequestParameters.count, TorrentPeer.maximumNumberOfPendingBlockRequests)
|
||||
}
|
||||
|
||||
func test_nextRequestMadeOnRecievingBlock() {
|
||||
let largePieceSize = TorrentPieceDownloadBuffer.blockSize * (TorrentPeer.maximumNumberOfPendingBlockRequests + 1)
|
||||
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: largePieceSize)
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
|
||||
guard let request = communicator.sendRequestParameters.first else { return }
|
||||
communicator.sendRequestParameters = []
|
||||
communicator.delegate?.peer(communicator,
|
||||
sentPiece: request.index,
|
||||
begin: request.begin,
|
||||
block: Data(repeating: 0, count: request.length))
|
||||
|
||||
XCTAssertEqual(communicator.sendRequestParameters.count, 1)
|
||||
}
|
||||
|
||||
func test_delegateNotifiedFailedToGetPiece_whenPeerChokes() {
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
communicator.delegate?.peerBecameChoked(communicator)
|
||||
|
||||
XCTAssert(delegate.failedToGetPieceAtIndexCalled)
|
||||
XCTAssert(delegate.failedToGetPieceAtIndexParameters?.sender === sut)
|
||||
XCTAssertEqual(delegate.failedToGetPieceAtIndexParameters?.index, pieceIndex)
|
||||
}
|
||||
|
||||
func test_delegateNotifiedOnSuccessfulPieceDownload() {
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
|
||||
let requests = communicator.sendRequestParameters.sorted(by: { $0.begin < $1.begin })
|
||||
var expectedResult: Data = Data()
|
||||
|
||||
var i: UInt8 = 1
|
||||
for request in requests {
|
||||
let block = Data(repeating: i, count: request.length)
|
||||
communicator.delegate?.peer(communicator,
|
||||
sentPiece: request.index,
|
||||
begin: request.begin,
|
||||
block: block)
|
||||
i += 1
|
||||
expectedResult += block
|
||||
}
|
||||
|
||||
XCTAssert(delegate.gotPieceAtIndexCalled)
|
||||
XCTAssert(delegate.gotPieceAtIndexParameters?.sender === sut)
|
||||
XCTAssertEqual(delegate.gotPieceAtIndexParameters?.index, pieceIndex)
|
||||
XCTAssertEqual(delegate.gotPieceAtIndexParameters?.piece, expectedResult)
|
||||
}
|
||||
|
||||
// MARK: - Peer connection lost
|
||||
|
||||
func test_delegateNotifiedOnPeerLost() {
|
||||
communicator.delegate?.peerLost(communicator)
|
||||
|
||||
XCTAssert(delegate.peerLostCalled)
|
||||
XCTAssert(delegate.peerLostParameter === sut)
|
||||
}
|
||||
|
||||
func test_delegateNotifiedFailedToGetPiece_whenPeerLost() {
|
||||
communicator.delegate?.peerBecameUnchoked(communicator)
|
||||
sut.downloadPiece(atIndex: pieceIndex, size: pieceSize)
|
||||
communicator.delegate?.peerLost(communicator)
|
||||
|
||||
XCTAssert(delegate.failedToGetPieceAtIndexCalled)
|
||||
XCTAssert(delegate.failedToGetPieceAtIndexParameters?.sender === sut)
|
||||
XCTAssertEqual(delegate.failedToGetPieceAtIndexParameters?.index, pieceIndex)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
}
|
||||
Reference in New Issue
Block a user