Uploading
This commit is contained in:
@@ -14,6 +14,8 @@
|
||||
B501A55B1F55894500B87911 /* TorrentPeerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501A55A1F55894400B87911 /* TorrentPeerTests.swift */; };
|
||||
B501A55D1F55898500B87911 /* TorrentUploadPieceRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501A55C1F55898500B87911 /* TorrentUploadPieceRequest.swift */; };
|
||||
B501A55F1F558BA800B87911 /* TorrentUploadPieceRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501A55E1F558BA800B87911 /* TorrentUploadPieceRequestTests.swift */; };
|
||||
B501A5611F558EFD00B87911 /* TorrentPeerUploadingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501A5601F558EFD00B87911 /* TorrentPeerUploadingTests.swift */; };
|
||||
B501A5661F55B24B00B87911 /* TorrentServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501A5651F55B24800B87911 /* TorrentServer.swift */; };
|
||||
B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */; };
|
||||
B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F81F0A554A00C23E7C /* UDPConnection.swift */; };
|
||||
B514DD7B1F40971D00C932F8 /* TorrentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B514DD7A1F40971D00C932F8 /* TorrentViewController.swift */; };
|
||||
@@ -175,6 +177,8 @@
|
||||
B501A55A1F55894400B87911 /* TorrentPeerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TorrentPeerTests.swift; sourceTree = "<group>"; };
|
||||
B501A55C1F55898500B87911 /* TorrentUploadPieceRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentUploadPieceRequest.swift; sourceTree = "<group>"; };
|
||||
B501A55E1F558BA800B87911 /* TorrentUploadPieceRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentUploadPieceRequestTests.swift; sourceTree = "<group>"; };
|
||||
B501A5601F558EFD00B87911 /* TorrentPeerUploadingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeerUploadingTests.swift; sourceTree = "<group>"; };
|
||||
B501A5651F55B24800B87911 /* TorrentServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentServer.swift; sourceTree = "<group>"; };
|
||||
B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnectionTests.swift; sourceTree = "<group>"; };
|
||||
B50B24F81F0A554A00C23E7C /* UDPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnection.swift; sourceTree = "<group>"; };
|
||||
B514DD7A1F40971D00C932F8 /* TorrentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -331,6 +335,14 @@
|
||||
path = "Piece Request info";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B501A5621F55B21000B87911 /* Torrent Server */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B501A5651F55B24800B87911 /* TorrentServer.swift */,
|
||||
);
|
||||
path = "Torrent Server";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B50B24F01F0A54C100C23E7C /* UDP Networking */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -481,6 +493,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B585AB771C3833450093FA41 /* BitTorrent.h */,
|
||||
B501A5621F55B21000B87911 /* Torrent Server */,
|
||||
B527F62F1F1BF2EB001F06AF /* Torrent Client */,
|
||||
B5BFD9C91F3FAF6C00CE0186 /* Tracker Manager */,
|
||||
B5F27C601F3F93950040589C /* Progress Manager */,
|
||||
@@ -625,6 +638,7 @@
|
||||
B5C06F121F12CDD8005730B3 /* TorrentPeer.swift */,
|
||||
B527F6281F1BCAD2001F06AF /* TorrentPeerDelegateStub.swift */,
|
||||
B501A55A1F55894400B87911 /* TorrentPeerTests.swift */,
|
||||
B501A5601F558EFD00B87911 /* TorrentPeerUploadingTests.swift */,
|
||||
B501A5591F55892300B87911 /* Piece Request info */,
|
||||
B527F62E1F1BDF39001F06AF /* Piece Download Buffer */,
|
||||
B5C06F101F12C2ED005730B3 /* Communicator */,
|
||||
@@ -1004,6 +1018,7 @@
|
||||
B54D0C7A1CA69FAD004343BD /* TorrentMetaInfo.swift in Sources */,
|
||||
B5C06F131F12CDD8005730B3 /* TorrentPeer.swift in Sources */,
|
||||
B5F81E4B1F04399800B25C70 /* TorrentTrackerResponse.swift in Sources */,
|
||||
B501A5661F55B24B00B87911 /* TorrentServer.swift in Sources */,
|
||||
B5AF7AA61F252A66003FD66F /* MultiFileHandle.swift in Sources */,
|
||||
B54D0C7B1CA69FD8004343BD /* Data+sha1.swift in Sources */,
|
||||
B5F27C5D1F3F7BDD0040589C /* TorrentPeerManager.swift in Sources */,
|
||||
@@ -1049,6 +1064,7 @@
|
||||
B501A55B1F55894500B87911 /* TorrentPeerTests.swift in Sources */,
|
||||
B5F27C641F3F93C20040589C /* TorrentProgressManagerTests.swift in Sources */,
|
||||
B527F62D1F1BD64D001F06AF /* TorrentPieceDownloadBufferTests.swift in Sources */,
|
||||
B501A5611F558EFD00B87911 /* TorrentPeerUploadingTests.swift in Sources */,
|
||||
B5F27C5F1F3F7BFD0040589C /* TorrentPeerManagerTests.swift in Sources */,
|
||||
B51638951F0EEAA4009E563E /* TCPConnectionTests.swift in Sources */,
|
||||
B527F6291F1BCAD2001F06AF /* TorrentPeerDelegateStub.swift in Sources */,
|
||||
|
||||
@@ -22,6 +22,8 @@ protocol TorrentPeerManagerDelegate: class {
|
||||
|
||||
func torrentPeerManager(_ sender: TorrentPeerManager,
|
||||
nextPieceFromAvailable availablePieces: BitField) -> TorrentPieceRequest?
|
||||
|
||||
func torrentPeerManager(_ sender: TorrentPeerManager, peerRequiresPieceAtIndex index: Int) -> Data?
|
||||
}
|
||||
|
||||
class TorrentPeerManager {
|
||||
@@ -76,6 +78,11 @@ class TorrentPeerManager {
|
||||
connectToPeersIfNeeded(peers: newPeers)
|
||||
}
|
||||
|
||||
func addPeer(_ peer: TorrentPeer) {
|
||||
peer.delegate = self
|
||||
peers.append(peer)
|
||||
}
|
||||
|
||||
fileprivate func connectToPeersIfNeeded(peers: [TorrentPeer]) {
|
||||
|
||||
guard let delegate = delegate else { return }
|
||||
@@ -110,10 +117,6 @@ extension TorrentPeerManager: TorrentPeerDelegate {
|
||||
return peers.filter { !$0.connected }
|
||||
}
|
||||
|
||||
func peerCompletedHandshake(_ sender: TorrentPeer) {
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
func peerHasNewAvailablePieces(_ sender: TorrentPeer) {
|
||||
if sender.numberOfPiecesDownloading < maximumNumberOfPiecesPerPeer {
|
||||
requestNextPiece(from: sender)
|
||||
@@ -137,6 +140,10 @@ extension TorrentPeerManager: TorrentPeerDelegate {
|
||||
delegate?.torrentPeerManager(self, failedToGetPieceAtIndex: index)
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeer, requestedPieceAtIndex index: Int) -> Data? {
|
||||
return delegate?.torrentPeerManager(self, peerRequiresPieceAtIndex: index)
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
private func requestNextPiece(from peer: TorrentPeer) {
|
||||
|
||||
@@ -77,6 +77,15 @@ class TorrentPeerManagerDelegateStub: TorrentPeerManagerDelegate {
|
||||
torrentPeerManagerCurrentBitfieldForHandshakeCalled = true
|
||||
return torrentPeerManagerCurrentBitfieldForHandshakeResult
|
||||
}
|
||||
|
||||
var peerRequiresPieceAtIndexCalled = false
|
||||
var peerRequiresPieceAtIndexParameters: (sender: TorrentPeerManager, index: Int)?
|
||||
var peerRequiresPieceAtIndexResult: Data?
|
||||
func torrentPeerManager(_ sender: TorrentPeerManager, peerRequiresPieceAtIndex index: Int) -> Data? {
|
||||
peerRequiresPieceAtIndexCalled = true
|
||||
peerRequiresPieceAtIndexParameters = (sender, index)
|
||||
return peerRequiresPieceAtIndexResult
|
||||
}
|
||||
}
|
||||
|
||||
class TorrentPeerManagerTests: XCTestCase {
|
||||
@@ -108,6 +117,12 @@ class TorrentPeerManagerTests: XCTestCase {
|
||||
return TorrentPeerFake(peerInfo: peerInfo, bitFieldSize: bitFieldSize, communicator: communicator)
|
||||
}
|
||||
|
||||
func createFakePeer() -> TorrentPeer {
|
||||
let peerInfo = TorrentPeerInfo(ip: "127.0.0.1", port: 123, peerId: nil)
|
||||
let communicator = TorrentPeerCommunicatorStub(peerInfo: peerInfo, infoHash: infoHash)
|
||||
return TorrentPeerFake(peerInfo: peerInfo, bitFieldSize: bitFieldSize, communicator: communicator)
|
||||
}
|
||||
|
||||
func test_addingPeerInfoCreatesPeers() {
|
||||
|
||||
// Given
|
||||
@@ -121,6 +136,14 @@ class TorrentPeerManagerTests: XCTestCase {
|
||||
XCTAssertEqual(sut.peers.first!.peerInfo, peerInfo)
|
||||
}
|
||||
|
||||
func test_canAddIndividualPeer() {
|
||||
let peer = createFakePeer()
|
||||
sut.addPeer(peer)
|
||||
XCTAssertEqual(sut.peers.count, 1)
|
||||
XCTAssert(sut.peers.first === peer)
|
||||
XCTAssert(sut.peers.first?.delegate === sut)
|
||||
}
|
||||
|
||||
func test_newPeersConnect() {
|
||||
|
||||
// Given
|
||||
@@ -205,7 +228,6 @@ class TorrentPeerManagerTests: XCTestCase {
|
||||
delegate.nextPieceFromAvailableResult = pieceRequest
|
||||
|
||||
// When
|
||||
sut.peerCompletedHandshake(peer)
|
||||
sut.peerHasNewAvailablePieces(peer)
|
||||
|
||||
// Then
|
||||
@@ -325,4 +347,26 @@ class TorrentPeerManagerTests: XCTestCase {
|
||||
XCTAssert(delegate.torrentPeerManagerNeedsMorePeersCalled)
|
||||
XCTAssert(delegate.torrentPeerManagerNeedsMorePeersParameter === sut)
|
||||
}
|
||||
|
||||
func test_delegateAskedForPieceToUpload() {
|
||||
|
||||
// Given
|
||||
let peerInfo = TorrentPeerInfo(ip: "127.0.0.1", port: 123, peerId: nil)
|
||||
sut.addPeers(withInfo: [peerInfo])
|
||||
guard let peer = peers.first else { return }
|
||||
|
||||
let data = Data(bytes: [1,2,3])
|
||||
delegate.peerRequiresPieceAtIndexResult = data
|
||||
|
||||
// When
|
||||
let result = sut.peer(peer, requestedPieceAtIndex: 123)
|
||||
|
||||
// Then
|
||||
XCTAssert(delegate.peerRequiresPieceAtIndexCalled)
|
||||
XCTAssertEqualData(result, data)
|
||||
if let parameters = delegate.peerRequiresPieceAtIndexParameters {
|
||||
XCTAssert(parameters.sender === sut)
|
||||
XCTAssertEqual(parameters.index, 123)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +75,10 @@ class TorrentPeerCommunicator {
|
||||
|
||||
weak var delegate: TorrentPeerCommunicatorDelegate?
|
||||
|
||||
var connected: Bool {
|
||||
return connection.connected
|
||||
}
|
||||
|
||||
private let peerInfo: TorrentPeerInfo
|
||||
private let connection: TCPConnectionProtocol
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
|
||||
class TorrentPeerCommunicatorStub: TorrentPeerCommunicator {
|
||||
|
||||
var testConnected: Bool = false
|
||||
override var connected: Bool {
|
||||
return testConnected
|
||||
}
|
||||
|
||||
var connectCalled = false
|
||||
override func connect() throws {
|
||||
connectCalled = true
|
||||
@@ -50,4 +55,15 @@ class TorrentPeerCommunicatorStub: TorrentPeerCommunicator {
|
||||
onSendKeepAliveCalled?()
|
||||
}
|
||||
|
||||
var sendPieceCallCount = 0
|
||||
var sendPieceParameters: (index: Int, begin: Int, block: Data, completion: (()->Void)?)?
|
||||
override func sendPiece(fromPieceAtIndex index: Int, begin: Int, block: Data, _ completion: (() -> Void)?) {
|
||||
sendPieceCallCount += 1
|
||||
sendPieceParameters = (index, begin, block, completion)
|
||||
}
|
||||
|
||||
var sendUnchokeCalled = false
|
||||
override func sendUnchoke(_ completion: (() -> Void)?) {
|
||||
sendUnchokeCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,6 +113,13 @@ class TorrentPeerComminicatorTests: XCTestCase {
|
||||
|
||||
// Mark - Tests
|
||||
|
||||
func test_connectedFlag() {
|
||||
tcpConnection.connected = false
|
||||
XCTAssertFalse(sut.connected)
|
||||
tcpConnection.connected = true
|
||||
XCTAssertTrue(sut.connected)
|
||||
}
|
||||
|
||||
func test_canConnect() {
|
||||
try! sut.connect()
|
||||
|
||||
|
||||
@@ -15,29 +15,33 @@ struct TorrentBlock {
|
||||
let data: Data
|
||||
}
|
||||
|
||||
struct TorrentUploadPieceRequest {
|
||||
class TorrentUploadPieceRequest {
|
||||
|
||||
private let data: Data
|
||||
private let index: Int
|
||||
private let length: Int
|
||||
private var blockRequests: [TorrentBlockRequest] = []
|
||||
|
||||
var hasBlockRequests: Bool {
|
||||
return blockRequests.first != nil
|
||||
}
|
||||
|
||||
init(data: Data, index: Int, length: Int) {
|
||||
init(data: Data, index: Int) {
|
||||
self.data = data
|
||||
self.index = index
|
||||
self.length = length
|
||||
}
|
||||
|
||||
mutating func addRequest(_ request: TorrentBlockRequest) {
|
||||
func addRequest(_ request: TorrentBlockRequest) {
|
||||
blockRequests.append(request)
|
||||
}
|
||||
|
||||
func removeRequest(_ request: TorrentBlockRequest) {
|
||||
guard let index = blockRequests.index(of: request) else { return }
|
||||
blockRequests.remove(at: index)
|
||||
}
|
||||
|
||||
func nextUploadBlock() -> TorrentBlock? {
|
||||
guard let request = self.blockRequests.first else { return nil }
|
||||
guard let request = blockRequests.first else { return nil }
|
||||
blockRequests.remove(at: 0)
|
||||
|
||||
let begin = request.begin
|
||||
let end = begin + request.length
|
||||
|
||||
@@ -13,16 +13,15 @@ class TorrentUploadPieceRequestTests: XCTestCase {
|
||||
|
||||
let data = Data(repeating: 1, count: 10) + Data(repeating: 2, count: 10)
|
||||
let index = 123
|
||||
let length = 20
|
||||
|
||||
func test_hasNoPendingRequestsOnInit() {
|
||||
let sut = TorrentUploadPieceRequest(data: data, index: index, length: length)
|
||||
let sut = TorrentUploadPieceRequest(data: data, index: index)
|
||||
XCTAssertFalse(sut.hasBlockRequests)
|
||||
XCTAssertNil(sut.nextUploadBlock())
|
||||
}
|
||||
|
||||
func test_nextUploadBlockReturnsCorrectData() {
|
||||
var sut = TorrentUploadPieceRequest(data: data, index: index, length: length)
|
||||
let sut = TorrentUploadPieceRequest(data: data, index: index)
|
||||
|
||||
let request = TorrentBlockRequest(piece: index, begin: 5, length: 10)
|
||||
sut.addRequest(request)
|
||||
@@ -40,4 +39,26 @@ class TorrentUploadPieceRequestTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
func test_cannotGetUploadBlockTwice() {
|
||||
let sut = TorrentUploadPieceRequest(data: data, index: index)
|
||||
|
||||
let request = TorrentBlockRequest(piece: index, begin: 5, length: 10)
|
||||
sut.addRequest(request)
|
||||
|
||||
_ = sut.nextUploadBlock()
|
||||
let result = sut.nextUploadBlock()
|
||||
|
||||
XCTAssertNil(result)
|
||||
}
|
||||
|
||||
func test_canRemoveBlockRequest() {
|
||||
let sut = TorrentUploadPieceRequest(data: data, index: index)
|
||||
|
||||
let request = TorrentBlockRequest(piece: index, begin: 5, length: 10)
|
||||
sut.addRequest(request)
|
||||
sut.removeRequest(request)
|
||||
|
||||
let result = sut.nextUploadBlock()
|
||||
XCTAssertNil(result, "Result should be nil")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,12 @@
|
||||
import Foundation
|
||||
|
||||
protocol TorrentPeerDelegate: class {
|
||||
func peerCompletedHandshake(_ sender: TorrentPeer)
|
||||
func peerLost(_ sender: TorrentPeer)
|
||||
func peerHasNewAvailablePieces(_ sender: TorrentPeer)
|
||||
func peer(_ sender: TorrentPeer, gotPieceAtIndex index: Int, piece: Data)
|
||||
func peer(_ sender: TorrentPeer, failedToGetPieceAtIndex index: Int)
|
||||
|
||||
func peer(_ sender: TorrentPeer, requestedPieceAtIndex index: Int) -> Data?
|
||||
}
|
||||
|
||||
class TorrentPeer {
|
||||
@@ -45,10 +46,16 @@ class TorrentPeer {
|
||||
|
||||
private var downloadPieceRequests: [Int: TorrentPieceDownloadBuffer] = [:]
|
||||
private var numberOfPendingBlockRequests = 0
|
||||
|
||||
private var uploadPieceRequests: [Int: TorrentUploadPieceRequest] = [:]
|
||||
private var currentlySendingBlock = false
|
||||
|
||||
private var handshakeData: (clientId: Data, bitField: BitField)?
|
||||
private var sentHandshake = false
|
||||
|
||||
private var keepAliveTimer: Timer?
|
||||
|
||||
var connected: Bool = false
|
||||
|
||||
private(set) var connected: Bool = false
|
||||
var numberOfPiecesDownloading: Int {
|
||||
return downloadPieceRequests.count
|
||||
}
|
||||
@@ -57,6 +64,7 @@ class TorrentPeer {
|
||||
self.peerInfo = peerInfo
|
||||
self.communicator = communicator
|
||||
self.currentProgress = BitField(size: bitFieldSize)
|
||||
self.connected = communicator.connected
|
||||
communicator.delegate = self
|
||||
}
|
||||
|
||||
@@ -65,12 +73,22 @@ class TorrentPeer {
|
||||
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()
|
||||
connected = true
|
||||
if !communicator.connected {
|
||||
try communicator.connect()
|
||||
connected = true
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func sendHandshakeIfNeeded() {
|
||||
guard let (clientId, bitField) = handshakeData, !sentHandshake else { return }
|
||||
|
||||
communicator.sendHandshake(for: clientId) { [weak self] in
|
||||
self?.sentHandshake = true
|
||||
self?.communicator.sendBitField(bitField)
|
||||
}
|
||||
}
|
||||
|
||||
func downloadPiece(atIndex index: Int, size: Int) {
|
||||
@@ -103,6 +121,56 @@ class TorrentPeer {
|
||||
}
|
||||
}
|
||||
|
||||
private func sendNextBlock() {
|
||||
guard let block = getNextBlockForUpload() else { return }
|
||||
|
||||
currentlySendingBlock = true
|
||||
communicator.sendPiece(fromPieceAtIndex: block.piece, begin: block.begin, block: block.data) { [weak self] in
|
||||
self?.currentlySendingBlock = false
|
||||
self?.sendNextBlock()
|
||||
}
|
||||
}
|
||||
|
||||
private func appendBlockRequest(_ blockRequest: TorrentBlockRequest) {
|
||||
let index = blockRequest.piece
|
||||
var pieceRequest: TorrentUploadPieceRequest
|
||||
if let previousRequest = uploadPieceRequests[index] {
|
||||
pieceRequest = previousRequest
|
||||
} else {
|
||||
guard let pieceData = delegate?.peer(self, requestedPieceAtIndex: index) else {
|
||||
if enableLogging { print ("Error - peer asked for a piece I don't have") }
|
||||
return
|
||||
}
|
||||
pieceRequest = TorrentUploadPieceRequest(data: pieceData, index: index)
|
||||
uploadPieceRequests[index] = pieceRequest
|
||||
}
|
||||
|
||||
pieceRequest.addRequest(blockRequest)
|
||||
}
|
||||
|
||||
private func getNextBlockForUpload() -> TorrentBlock? {
|
||||
guard
|
||||
!currentlySendingBlock,
|
||||
let (pieceIndex, pieceRequest) = uploadPieceRequests.first,
|
||||
let block = pieceRequest.nextUploadBlock() else { return nil }
|
||||
|
||||
if !pieceRequest.hasBlockRequests {
|
||||
uploadPieceRequests[pieceIndex] = nil
|
||||
}
|
||||
|
||||
return block
|
||||
}
|
||||
|
||||
private func cancelBlockRequest(_ blockRequest: TorrentBlockRequest) {
|
||||
let pieceIndex = blockRequest.piece
|
||||
guard let pieceRequest = uploadPieceRequests[pieceIndex] else { return }
|
||||
pieceRequest.removeRequest(blockRequest)
|
||||
|
||||
if !pieceRequest.hasBlockRequests {
|
||||
uploadPieceRequests[pieceIndex] = nil
|
||||
}
|
||||
}
|
||||
|
||||
private func killAllDownloads() {
|
||||
for downloadPieceRequest in downloadPieceRequests {
|
||||
delegate?.peer(self, failedToGetPieceAtIndex: downloadPieceRequest.value.index)
|
||||
@@ -110,10 +178,15 @@ class TorrentPeer {
|
||||
downloadPieceRequests.removeAll()
|
||||
}
|
||||
|
||||
private func killAllUploads() {
|
||||
uploadPieceRequests.removeAll()
|
||||
}
|
||||
|
||||
private func onConnectionDropped() {
|
||||
keepAliveTimer?.invalidate()
|
||||
keepAliveTimer = nil
|
||||
killAllDownloads()
|
||||
killAllUploads()
|
||||
connected = false
|
||||
delegate?.peerLost(self)
|
||||
}
|
||||
@@ -157,14 +230,8 @@ extension TorrentPeer {
|
||||
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)
|
||||
}
|
||||
sendHandshakeIfNeeded()
|
||||
}
|
||||
|
||||
func peerLost(_ sender: TorrentPeerCommunicator) {
|
||||
@@ -173,7 +240,7 @@ extension TorrentPeer: TorrentPeerCommunicatorDelegate {
|
||||
|
||||
func peerSentHandshake(_ sender: TorrentPeerCommunicator, sentHandshakeWithPeerId peerId: Data, onDHT: Bool) {
|
||||
startKeepAlive()
|
||||
delegate?.peerCompletedHandshake(self)
|
||||
sendHandshakeIfNeeded()
|
||||
}
|
||||
|
||||
func peerSentKeepAlive(_ sender: TorrentPeerCommunicator) {
|
||||
@@ -183,6 +250,7 @@ extension TorrentPeer: TorrentPeerCommunicatorDelegate {
|
||||
func peerBecameChoked(_ sender: TorrentPeerCommunicator) {
|
||||
peerChoked = true
|
||||
killAllDownloads()
|
||||
killAllUploads()
|
||||
}
|
||||
|
||||
func peerBecameUnchoked(_ sender: TorrentPeerCommunicator) {
|
||||
@@ -192,10 +260,12 @@ extension TorrentPeer: TorrentPeerCommunicatorDelegate {
|
||||
|
||||
func peerBecameInterested(_ sender: TorrentPeerCommunicator) {
|
||||
peerInterested = true
|
||||
communicator.sendUnchoke()
|
||||
}
|
||||
|
||||
func peerBecameUninterested(_ sender: TorrentPeerCommunicator) {
|
||||
peerInterested = false
|
||||
killAllUploads()
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, hasPiece piece: Int) {
|
||||
@@ -209,7 +279,9 @@ extension TorrentPeer: TorrentPeerCommunicatorDelegate {
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, requestedPiece index: Int, begin: Int, length: Int) {
|
||||
|
||||
let blockRequest = TorrentBlockRequest(piece: index, begin: begin, length: length)
|
||||
appendBlockRequest(blockRequest)
|
||||
sendNextBlock()
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, sentPiece index: Int, begin: Int, block: Data) {
|
||||
@@ -226,7 +298,8 @@ extension TorrentPeer: TorrentPeerCommunicatorDelegate {
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, cancelledRequestedPiece index: Int, begin: Int, length: Int) {
|
||||
|
||||
let blockRequest = TorrentBlockRequest(piece: index, begin: begin, length: length)
|
||||
cancelBlockRequest(blockRequest)
|
||||
}
|
||||
|
||||
func peer(_ sender: TorrentPeerCommunicator, onDHTPort port: Int) {
|
||||
|
||||
@@ -10,13 +10,6 @@
|
||||
|
||||
class TorrentPeerDelegateStub: TorrentPeerDelegate {
|
||||
|
||||
var peerCompletedHandshakeCalled = false
|
||||
var peerCompletedHandshakeParameter: TorrentPeer?
|
||||
func peerCompletedHandshake(_ sender: TorrentPeer) {
|
||||
peerCompletedHandshakeCalled = true
|
||||
peerCompletedHandshakeParameter = sender
|
||||
}
|
||||
|
||||
var peerHasNewAvailablePiecesCalled = false
|
||||
var peerHasNewAvailablePiecesParameter: TorrentPeer?
|
||||
func peerHasNewAvailablePieces(_ sender: TorrentPeer) {
|
||||
@@ -44,4 +37,13 @@ class TorrentPeerDelegateStub: TorrentPeerDelegate {
|
||||
gotPieceAtIndexCalled = true
|
||||
gotPieceAtIndexParameters = (sender, index, piece)
|
||||
}
|
||||
|
||||
var requestedPieceAtIndexCalled = false
|
||||
var requestedPieceAtIndexParameters: (sender: TorrentPeer, index: Int)?
|
||||
var requestedPieceAtIndexResult: Data?
|
||||
func peer(_ sender: TorrentPeer, requestedPieceAtIndex index: Int) -> Data? {
|
||||
requestedPieceAtIndexCalled = true
|
||||
requestedPieceAtIndexParameters = (sender, index)
|
||||
return requestedPieceAtIndexResult
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,13 @@ class TorrentPeerTests: XCTestCase {
|
||||
XCTAssertFalse(sut.peerInterested)
|
||||
XCTAssertTrue(sut.amChokedToPeer)
|
||||
XCTAssertFalse(sut.amInterestedInPeer)
|
||||
XCTAssertFalse(sut.connected)
|
||||
}
|
||||
|
||||
func test_creationWithConnectedSocket() {
|
||||
communicator.testConnected = true
|
||||
sut = TorrentPeer(peerInfo: peerInfo, bitFieldSize: 10, communicator: communicator)
|
||||
XCTAssertTrue(sut.connected)
|
||||
}
|
||||
|
||||
// MARK: - Connection + handshake
|
||||
@@ -69,6 +76,22 @@ class TorrentPeerTests: XCTestCase {
|
||||
XCTAssertEqual(communicator.sendHandshakeParameters?.clientId, clientId)
|
||||
}
|
||||
|
||||
func test_handshakeSentOnRecievingHandshakeFromLeacher() {
|
||||
|
||||
// Given
|
||||
communicator.testConnected = true
|
||||
sut = TorrentPeer(peerInfo: peerInfo, bitFieldSize: 10, communicator: communicator)
|
||||
|
||||
// When
|
||||
try! sut.connect(withHandshakeData: (clientId, bitField))
|
||||
communicator.delegate?.peerSentHandshake(communicator, sentHandshakeWithPeerId: peerId, onDHT: false)
|
||||
|
||||
// Then
|
||||
XCTAssertFalse(communicator.connectCalled)
|
||||
XCTAssert(communicator.sendHandshakeCalled)
|
||||
XCTAssertEqual(communicator.sendHandshakeParameters?.clientId, clientId)
|
||||
}
|
||||
|
||||
func test_bitFieldSentAfterHandshake() {
|
||||
|
||||
// Given
|
||||
@@ -87,15 +110,6 @@ class TorrentPeerTests: XCTestCase {
|
||||
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)
|
||||
}
|
||||
|
||||
func test_delegateNotifiedAfterBitField() {
|
||||
communicator.delegate?.peer(communicator, hasBitField: bitField)
|
||||
|
||||
@@ -332,7 +346,7 @@ class TorrentPeerTests: XCTestCase {
|
||||
XCTAssertFalse(self.sut.connected)
|
||||
e.fulfill()
|
||||
}
|
||||
waitForExpectations(timeout: 0.1)
|
||||
waitForExpectations(timeout: 0.2)
|
||||
}
|
||||
|
||||
func test_staysConnectedIfKeepAliveSent() {
|
||||
@@ -354,6 +368,11 @@ class TorrentPeerTests: XCTestCase {
|
||||
waitForExpectations(timeout: 0.1)
|
||||
}
|
||||
|
||||
func test_unchokeSentOnPeerInterested() {
|
||||
sut.peerBecameInterested(communicator)
|
||||
XCTAssert(communicator.sendUnchokeCalled)
|
||||
}
|
||||
|
||||
// MARK: - Speed trackers
|
||||
|
||||
func test_gotPieceRecordedInSpeedTracker() {
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
//
|
||||
// TorrentPeerUploadingTests.swift
|
||||
// BitTorrentTests
|
||||
//
|
||||
// Created by Ben Davis on 29/08/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import BitTorrent
|
||||
|
||||
class TorrentPeerUploadingTests: 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 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_pieceSentOnRequest() {
|
||||
|
||||
// Given
|
||||
let pieceIndex = 123
|
||||
let begin = 0
|
||||
let data = Data(repeating: 4, count: 10)
|
||||
delegate.requestedPieceAtIndexResult = data
|
||||
|
||||
// When
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin, length: 10)
|
||||
|
||||
// Then
|
||||
XCTAssert(delegate.requestedPieceAtIndexCalled)
|
||||
XCTAssertEqual(communicator.sendPieceCallCount, 1)
|
||||
if let sendPieceParameters = communicator.sendPieceParameters {
|
||||
XCTAssertEqual(sendPieceParameters.index, pieceIndex)
|
||||
XCTAssertEqual(sendPieceParameters.begin, begin)
|
||||
XCTAssertEqualData(sendPieceParameters.block, data)
|
||||
}
|
||||
}
|
||||
|
||||
func test_blocksSentOneByOne() {
|
||||
|
||||
// Given
|
||||
let pieceIndex = 123
|
||||
let begin1 = 0
|
||||
let begin2 = 10
|
||||
let data = Data(repeating: 4, count: 10)
|
||||
delegate.requestedPieceAtIndexResult = data
|
||||
|
||||
// When
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin1, length: 10)
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin2, length: 10)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(communicator.sendPieceCallCount, 1)
|
||||
|
||||
// And When
|
||||
guard let sendPieceParameters = communicator.sendPieceParameters else { return }
|
||||
sendPieceParameters.completion?()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(communicator.sendPieceCallCount, 2)
|
||||
if let sendPieceParameters2 = communicator.sendPieceParameters {
|
||||
XCTAssertEqual(sendPieceParameters2.begin, begin2)
|
||||
}
|
||||
}
|
||||
|
||||
func test_canRequestMultiplePieces() {
|
||||
|
||||
// Given
|
||||
let pieceIndex1 = 1
|
||||
let pieceIndex2 = 2
|
||||
let data = Data(repeating: 0, count: 10)
|
||||
delegate.requestedPieceAtIndexResult = data
|
||||
|
||||
// When
|
||||
sut.peer(communicator, requestedPiece: pieceIndex1, begin: 0, length: 10)
|
||||
sut.peer(communicator, requestedPiece: pieceIndex1, begin: 10, length: 10)
|
||||
sut.peer(communicator, requestedPiece: pieceIndex2, begin: 0, length: 10)
|
||||
|
||||
communicator.sendPieceParameters?.completion?()
|
||||
communicator.sendPieceParameters?.completion?()
|
||||
communicator.sendPieceParameters?.completion?()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(communicator.sendPieceCallCount, 3)
|
||||
}
|
||||
|
||||
func test_peerCanCancelUnsentBlock() {
|
||||
|
||||
// Given
|
||||
let pieceIndex = 123
|
||||
let begin1 = 0
|
||||
let begin2 = 10
|
||||
let data = Data(repeating: 4, count: 10)
|
||||
delegate.requestedPieceAtIndexResult = data
|
||||
|
||||
// When
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin1, length: 10)
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin2, length: 10)
|
||||
sut.peer(communicator, cancelledRequestedPiece: pieceIndex, begin: begin2, length: 10)
|
||||
|
||||
// And When
|
||||
communicator.sendPieceParameters?.completion?()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(communicator.sendPieceCallCount, 1)
|
||||
}
|
||||
|
||||
func test_onLostPeerUploadStops() {
|
||||
|
||||
// Given
|
||||
let pieceIndex = 123
|
||||
let begin1 = 0
|
||||
let begin2 = 10
|
||||
let data = Data(repeating: 4, count: 10)
|
||||
delegate.requestedPieceAtIndexResult = data
|
||||
|
||||
// When
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin1, length: 10)
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin2, length: 10)
|
||||
sut.peerLost(communicator)
|
||||
|
||||
// And When
|
||||
communicator.sendPieceParameters?.completion?()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(communicator.sendPieceCallCount, 1)
|
||||
}
|
||||
|
||||
func test_onPeerChokedUploadsCancelled() {
|
||||
|
||||
// Given
|
||||
let pieceIndex = 123
|
||||
let begin1 = 0
|
||||
let begin2 = 10
|
||||
let data = Data(repeating: 4, count: 10)
|
||||
delegate.requestedPieceAtIndexResult = data
|
||||
|
||||
// When
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin1, length: 10)
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin2, length: 10)
|
||||
sut.peerBecameChoked(communicator)
|
||||
|
||||
// Even if
|
||||
sut.peerBecameUnchoked(communicator)
|
||||
|
||||
// Then When
|
||||
communicator.sendPieceParameters?.completion?()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(communicator.sendPieceCallCount, 1)
|
||||
}
|
||||
|
||||
func test_onPeerUninterestedUploadsCancelled() {
|
||||
|
||||
// Given
|
||||
let pieceIndex = 123
|
||||
let begin1 = 0
|
||||
let begin2 = 10
|
||||
let data = Data(repeating: 4, count: 10)
|
||||
delegate.requestedPieceAtIndexResult = data
|
||||
|
||||
// When
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin1, length: 10)
|
||||
sut.peer(communicator, requestedPiece: pieceIndex, begin: begin2, length: 10)
|
||||
sut.peerBecameUninterested(communicator)
|
||||
|
||||
// Even if
|
||||
sut.peerBecameUnchoked(communicator)
|
||||
|
||||
// Then When
|
||||
communicator.sendPieceParameters?.completion?()
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(communicator.sendPieceCallCount, 1)
|
||||
}
|
||||
}
|
||||
@@ -36,4 +36,9 @@ class GCDAsyncSocketStub: GCDAsyncSocket {
|
||||
writeCalled = true
|
||||
writeParameters = (data, timeout, tag)
|
||||
}
|
||||
|
||||
var testIsConnected = false
|
||||
override var isConnected: Bool {
|
||||
return testIsConnected
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ protocol TCPConnectionProtocol: class {
|
||||
var connectedHost: String? { get }
|
||||
var connectedPort: UInt16? { get }
|
||||
|
||||
var connected: Bool { get }
|
||||
func connect(to host: String, onPort port: UInt16) throws
|
||||
func disconnect()
|
||||
|
||||
@@ -53,6 +54,10 @@ class TCPConnection: NSObject, TCPConnectionProtocol {
|
||||
return socket.connectedPort
|
||||
}
|
||||
|
||||
var connected: Bool {
|
||||
return socket.isConnected
|
||||
}
|
||||
|
||||
init(socket: GCDAsyncSocket = GCDAsyncSocket()) {
|
||||
self.socket = socket
|
||||
super.init()
|
||||
|
||||
@@ -15,6 +15,7 @@ class TCPConnectionStub: TCPConnectionProtocol {
|
||||
|
||||
var connectedHost: String?
|
||||
var connectedPort: UInt16?
|
||||
var connected: Bool = false
|
||||
|
||||
var connectCalled = false
|
||||
var connectParameters: (host: String, port: UInt16)?
|
||||
|
||||
@@ -191,4 +191,11 @@ class TCPConnectionTests: XCTestCase {
|
||||
XCTAssertEqual(delegateStub.disconnectedWithErrorParameters?.sender, sut)
|
||||
XCTAssertEqual(delegateStub.disconnectedWithErrorParameters?.error as? MyError, error)
|
||||
}
|
||||
|
||||
func test_connectedFlag() {
|
||||
socket.testIsConnected = false
|
||||
XCTAssertFalse(sut.connected)
|
||||
socket.testIsConnected = true
|
||||
XCTAssertTrue(sut.connected)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,32 +40,42 @@ public class TorrentClient {
|
||||
let progressManager: TorrentProgressManager
|
||||
let peerManager: TorrentPeerManager
|
||||
let trackerManager: TorrentTrackerManager
|
||||
let torrentServer: TorrentServer
|
||||
|
||||
let clientId = TorrentPeer.makePeerId()
|
||||
|
||||
public init(metaInfo: TorrentMetaInfo, rootDirectory: String) {
|
||||
self.metaInfo = metaInfo
|
||||
self.torrentServer = TorrentServer(infoHash: metaInfo.infoHash, clientId: clientId)
|
||||
self.progressManager = TorrentProgressManager(metaInfo: metaInfo, rootDirectory: rootDirectory)
|
||||
self.peerManager = TorrentPeerManager(clientId: clientId,
|
||||
infoHash: metaInfo.infoHash,
|
||||
bitFieldSize: metaInfo.info.pieces.count)
|
||||
|
||||
// TODO: listen on this port
|
||||
trackerManager = TorrentTrackerManager(metaInfo: metaInfo, clientId: clientId, port: 123)
|
||||
trackerManager = TorrentTrackerManager(metaInfo: metaInfo, clientId: clientId, port: torrentServer.port)
|
||||
|
||||
trackerManager.delegate = self
|
||||
peerManager.delegate = self
|
||||
torrentServer.delegate = self
|
||||
}
|
||||
|
||||
// For testing
|
||||
init(metaInfo: TorrentMetaInfo,
|
||||
torrentServer: TorrentServer,
|
||||
progressManager: TorrentProgressManager,
|
||||
peerManager: TorrentPeerManager,
|
||||
trackerManager: TorrentTrackerManager) {
|
||||
|
||||
self.metaInfo = metaInfo
|
||||
self.torrentServer = torrentServer
|
||||
self.progressManager = progressManager
|
||||
self.peerManager = peerManager
|
||||
self.trackerManager = trackerManager
|
||||
|
||||
trackerManager.delegate = self
|
||||
peerManager.delegate = self
|
||||
torrentServer.delegate = self
|
||||
}
|
||||
|
||||
public func forceReCheck() {
|
||||
@@ -73,6 +83,7 @@ public class TorrentClient {
|
||||
}
|
||||
|
||||
public func start() {
|
||||
torrentServer.startListening()
|
||||
trackerManager.start()
|
||||
status = .started
|
||||
}
|
||||
@@ -81,7 +92,9 @@ public class TorrentClient {
|
||||
extension TorrentClient: TorrentTrackerManagerDelegate {
|
||||
|
||||
func torrentTrackerManager(_ sender: TorrentTrackerManager, gotNewPeers peers: [TorrentPeerInfo]) {
|
||||
peerManager.addPeers(withInfo: peers)
|
||||
if !progress.complete {
|
||||
peerManager.addPeers(withInfo: peers)
|
||||
}
|
||||
}
|
||||
|
||||
func torrentTrackerManagerAnnonuceInfo(_ sender: TorrentTrackerManager) -> TorrentTrackerManagerAnnonuceInfo {
|
||||
@@ -120,5 +133,18 @@ extension TorrentClient: TorrentPeerManagerDelegate {
|
||||
nextPieceFromAvailable availablePieces: BitField) -> TorrentPieceRequest? {
|
||||
return progressManager.getNextPieceToDownload(from: availablePieces)
|
||||
}
|
||||
|
||||
func torrentPeerManager(_ sender: TorrentPeerManager, peerRequiresPieceAtIndex index: Int) -> Data? {
|
||||
return progressManager.fileManager.getPiece(at: index)
|
||||
}
|
||||
}
|
||||
|
||||
extension TorrentClient: TorrentServerDelegate {
|
||||
func torrentServer(_ torrentServer: TorrentServer, connectedToPeer peer: TorrentPeer) {
|
||||
peerManager.addPeer(peer)
|
||||
}
|
||||
|
||||
func currentProgress(for torrentServer: TorrentServer) -> BitField {
|
||||
return progress.bitField
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,24 @@
|
||||
|
||||
@testable import BitTorrent
|
||||
|
||||
class TorrentServerStub: TorrentServer {
|
||||
init(metaInfo: TorrentMetaInfo) {
|
||||
let clientId = Data(repeating: 1, count: 20)
|
||||
super.init(infoHash: metaInfo.infoHash, clientId: clientId)
|
||||
}
|
||||
|
||||
var startListeningCalled = false
|
||||
override func startListening() {
|
||||
startListeningCalled = true
|
||||
}
|
||||
}
|
||||
|
||||
class TorrentProgressManagerStub: TorrentProgressManager {
|
||||
|
||||
let fileHandle: FileHandleFake
|
||||
|
||||
init(metaInfo: TorrentMetaInfo) {
|
||||
let fileHandle = FileHandleFake(data: Data(repeating: 0, count: metaInfo.info.length))
|
||||
fileHandle = FileHandleFake(data: Data(repeating: 0, count: metaInfo.info.length))
|
||||
let fileManager = TorrentFileManager(metaInfo: metaInfo, rootDirectory: "/", fileHandles: [fileHandle])
|
||||
let progress = TorrentProgress(size: metaInfo.info.pieces.count)
|
||||
super.init(fileManager: fileManager, progress: progress)
|
||||
@@ -50,8 +64,7 @@ class TorrentPeerManagerStub: TorrentPeerManager {
|
||||
|
||||
init(metaInfo: TorrentMetaInfo) {
|
||||
let clientId = Data(repeating: 1, count: 20)
|
||||
let infoHash = Data(repeating: 2, count: 20)
|
||||
super.init(clientId: clientId, infoHash: infoHash, bitFieldSize: metaInfo.info.pieces.count)
|
||||
super.init(clientId: clientId, infoHash: metaInfo.infoHash, bitFieldSize: metaInfo.info.pieces.count)
|
||||
}
|
||||
|
||||
var addPeersCalled = false
|
||||
@@ -60,6 +73,13 @@ class TorrentPeerManagerStub: TorrentPeerManager {
|
||||
addPeersCalled = true
|
||||
addPeersParameter = peerInfos
|
||||
}
|
||||
|
||||
var addPeerCalled = false
|
||||
var addPeerParameter: TorrentPeer? = nil
|
||||
override func addPeer(_ peer: TorrentPeer) {
|
||||
addPeerCalled = true
|
||||
addPeerParameter = peer
|
||||
}
|
||||
}
|
||||
|
||||
class TorrentTrackerManagerStub: TorrentTrackerManager {
|
||||
|
||||
@@ -25,6 +25,7 @@ class TorrentClientTests: XCTestCase {
|
||||
return try! Data(contentsOf: URL(fileURLWithPath: path!))
|
||||
}()
|
||||
|
||||
var torrentServer: TorrentServerStub!
|
||||
var progressManager: TorrentProgressManagerStub!
|
||||
var peerManager: TorrentPeerManagerStub!
|
||||
var trackerManager: TorrentTrackerManagerStub!
|
||||
@@ -34,11 +35,13 @@ class TorrentClientTests: XCTestCase {
|
||||
super.setUp()
|
||||
try! TorrentFileManager.prepareRootDirectory(pathRoot + "/text/", forTorrentMetaInfo: metaInfo)
|
||||
|
||||
torrentServer = TorrentServerStub(metaInfo: metaInfo)
|
||||
progressManager = TorrentProgressManagerStub(metaInfo: metaInfo)
|
||||
peerManager = TorrentPeerManagerStub(metaInfo: metaInfo)
|
||||
trackerManager = TorrentTrackerManagerStub(metaInfo: metaInfo)
|
||||
|
||||
sut = TorrentClient(metaInfo: metaInfo,
|
||||
torrentServer: torrentServer,
|
||||
progressManager: progressManager,
|
||||
peerManager: peerManager,
|
||||
trackerManager: trackerManager)
|
||||
@@ -48,10 +51,16 @@ class TorrentClientTests: XCTestCase {
|
||||
let sut = TorrentClient(metaInfo: metaInfo, rootDirectory: pathRoot)
|
||||
|
||||
XCTAssertEqual(sut.metaInfo.infoHash, metaInfo.infoHash)
|
||||
XCTAssert(sut.torrentServer.delegate === sut)
|
||||
XCTAssert(sut.trackerManager.delegate === sut)
|
||||
XCTAssert(sut.peerManager.delegate === sut)
|
||||
}
|
||||
|
||||
func test_torrentServerStartsListeningOnTorrentStart() {
|
||||
sut.start()
|
||||
XCTAssert(torrentServer.startListeningCalled)
|
||||
}
|
||||
|
||||
func test_trackerAnnounceOnTorrentStart() {
|
||||
sut.start()
|
||||
XCTAssert(trackerManager.startCalled)
|
||||
@@ -167,4 +176,55 @@ class TorrentClientTests: XCTestCase {
|
||||
XCTAssertEqual(result.size, expected.size)
|
||||
XCTAssertEqualData(result.checksum, expected.checksum)
|
||||
}
|
||||
|
||||
func test_pieceForUploadComesFromFileManager() {
|
||||
|
||||
progressManager.fileHandle.seek(toFileOffset: 0)
|
||||
progressManager.fileHandle.write(finalData)
|
||||
let result = sut.torrentPeerManager(peerManager, peerRequiresPieceAtIndex: 0)
|
||||
XCTAssertEqualData(result, finalData)
|
||||
}
|
||||
|
||||
func test_peersConnectingFromServerAreAddedToPeerManager() {
|
||||
|
||||
// Given
|
||||
let peer = createFakePeer()
|
||||
|
||||
// When
|
||||
sut.torrentServer(torrentServer, connectedToPeer: peer)
|
||||
|
||||
// Then
|
||||
XCTAssert(peerManager.addPeerCalled)
|
||||
if let addPeerParameter = peerManager.addPeerParameter {
|
||||
XCTAssert(addPeerParameter === peer)
|
||||
}
|
||||
}
|
||||
|
||||
func createFakePeer() -> TorrentPeer {
|
||||
let peerInfo = TorrentPeerInfo(ip: "127.0.0.1", port: 123, peerId: nil)
|
||||
let communicator = TorrentPeerCommunicatorStub(peerInfo: peerInfo, infoHash: metaInfo.infoHash)
|
||||
return TorrentPeerFake(peerInfo: peerInfo,
|
||||
bitFieldSize: metaInfo.info.pieces.count,
|
||||
communicator: communicator)
|
||||
}
|
||||
|
||||
func test_currentProgressForTorrentServer() {
|
||||
|
||||
// Given
|
||||
var progress = TorrentProgress(size: 5)
|
||||
|
||||
progress.setCurrentlyDownloading(piece: 0)
|
||||
progress.finishedDownloading(piece: 0)
|
||||
|
||||
progress.setCurrentlyDownloading(piece: 1)
|
||||
progress.finishedDownloading(piece: 1)
|
||||
|
||||
progressManager.testProgress = progress
|
||||
|
||||
// When
|
||||
let result = sut.currentProgress(for: torrentServer)
|
||||
|
||||
// Then
|
||||
XCTAssertEqual(result, progress.bitField)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// TorrentServer.swift
|
||||
// BitTorrent
|
||||
//
|
||||
// Created by Ben Davis on 29/08/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CocoaAsyncSocket
|
||||
|
||||
protocol TorrentServerDelegate: class {
|
||||
func torrentServer(_ torrentServer: TorrentServer, connectedToPeer peer: TorrentPeer)
|
||||
func currentProgress(for torrentServer: TorrentServer) -> BitField
|
||||
}
|
||||
|
||||
class TorrentServer: NSObject, GCDAsyncSocketDelegate {
|
||||
|
||||
weak var delegate: TorrentServerDelegate?
|
||||
var listenSocket: GCDAsyncSocket!
|
||||
let infoHash: Data
|
||||
let clientId: Data
|
||||
let port: UInt16 = 6881
|
||||
|
||||
init(infoHash: Data, clientId: Data) {
|
||||
self.infoHash = infoHash
|
||||
self.clientId = clientId
|
||||
super.init()
|
||||
self.listenSocket = GCDAsyncSocket(delegate: self, delegateQueue: DispatchQueue.main)
|
||||
}
|
||||
|
||||
deinit {
|
||||
listenSocket.delegate = nil
|
||||
listenSocket.disconnect()
|
||||
}
|
||||
|
||||
func startListening() {
|
||||
do {
|
||||
try listenSocket.accept(onPort: port)
|
||||
} catch _ {
|
||||
print("Couldn't listen on port to accept incoming peers")
|
||||
}
|
||||
}
|
||||
|
||||
func socket(_ sock: GCDAsyncSocket, didAcceptNewSocket newSocket: GCDAsyncSocket) {
|
||||
guard let delegate = self.delegate else { return }
|
||||
let peerInfo = TorrentPeerInfo(ip: newSocket.connectedHost!, port: newSocket.connectedPort, peerId: nil)
|
||||
let tcpConnection = TCPConnection(socket: newSocket)
|
||||
let communicator = TorrentPeerCommunicator(peerInfo: peerInfo, infoHash: infoHash, tcpConnection: tcpConnection)
|
||||
let currentProgress = delegate.currentProgress(for: self)
|
||||
let peer = TorrentPeer(peerInfo: peerInfo, bitFieldSize: currentProgress.size, communicator: communicator)
|
||||
peer.enableLogging = true
|
||||
try! peer.connect(withHandshakeData: (clientId: clientId, bitField: currentProgress))
|
||||
delegate.torrentServer(self, connectedToPeer: peer)
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class TorrentTrackerManager {
|
||||
|
||||
let metaInfo: TorrentMetaInfo
|
||||
let clientId: String
|
||||
let port: Int
|
||||
let port: UInt16
|
||||
|
||||
var announceTimeInterval: TimeInterval = 600
|
||||
private lazy var announceTimer: Timer = {
|
||||
@@ -39,7 +39,7 @@ class TorrentTrackerManager {
|
||||
repeats: true)
|
||||
}()
|
||||
|
||||
init(metaInfo: TorrentMetaInfo, clientId: Data, port: Int) {
|
||||
init(metaInfo: TorrentMetaInfo, clientId: Data, port: UInt16) {
|
||||
self.metaInfo = metaInfo
|
||||
self.clientId = String(data: clientId, encoding: .utf8)!
|
||||
self.port = port
|
||||
@@ -51,7 +51,7 @@ class TorrentTrackerManager {
|
||||
}
|
||||
|
||||
// For testing
|
||||
init(metaInfo: TorrentMetaInfo, clientId: Data, port: Int, trackers: [TorrentTracker]) {
|
||||
init(metaInfo: TorrentMetaInfo, clientId: Data, port: UInt16, trackers: [TorrentTracker]) {
|
||||
self.metaInfo = metaInfo
|
||||
self.clientId = String(data: clientId, encoding: .utf8)!
|
||||
self.port = port
|
||||
|
||||
@@ -15,7 +15,7 @@ class TorrentTrackerStub: TorrentTracker {
|
||||
|
||||
var announceClientCalled = false
|
||||
var announceClientParameters: (peerId: String,
|
||||
port: Int,
|
||||
port: UInt16,
|
||||
event: TorrentTrackerEvent,
|
||||
infoHash: Data,
|
||||
numberOfBytesRemaining: Int,
|
||||
@@ -25,7 +25,7 @@ class TorrentTrackerStub: TorrentTracker {
|
||||
|
||||
var onAnnounceClient: (()->Void)?
|
||||
func announceClient(with peerId: String,
|
||||
port: Int,
|
||||
port: UInt16,
|
||||
event: TorrentTrackerEvent,
|
||||
infoHash: Data,
|
||||
numberOfBytesRemaining: Int,
|
||||
@@ -73,7 +73,7 @@ class TorrentTrackerManagerTests: XCTestCase {
|
||||
|
||||
let clientId = "-BD0000-bxa]N#IRKqv`"
|
||||
let clientIdData = "-BD0000-bxa]N#IRKqv`".data(using: .ascii)!
|
||||
let listeningPort = 123
|
||||
let listeningPort: UInt16 = 123
|
||||
|
||||
|
||||
let announceInfo = TorrentTrackerManagerAnnonuceInfo(numberOfBytesRemaining: 1,
|
||||
|
||||
@@ -21,7 +21,7 @@ class TorrentHTTPTracker: TorrentTracker {
|
||||
}
|
||||
|
||||
func announceClient(with peerId: String,
|
||||
port: Int,
|
||||
port: UInt16,
|
||||
event: TorrentTrackerEvent = .started,
|
||||
infoHash: Data,
|
||||
numberOfBytesRemaining: Int,
|
||||
|
||||
@@ -13,7 +13,7 @@ protocol TorrentTracker: class {
|
||||
weak var delegate: TorrentTrackerDelegate? { get set }
|
||||
|
||||
func announceClient(with peerId: String,
|
||||
port: Int,
|
||||
port: UInt16,
|
||||
event: TorrentTrackerEvent,
|
||||
infoHash: Data,
|
||||
numberOfBytesRemaining: Int,
|
||||
|
||||
@@ -40,7 +40,7 @@ class TorrentUDPTracker: TorrentTracker {
|
||||
}
|
||||
|
||||
func announceClient(with peerId: String,
|
||||
port: Int,
|
||||
port: UInt16,
|
||||
event: TorrentTrackerEvent = .started,
|
||||
infoHash: Data,
|
||||
numberOfBytesRemaining: Int,
|
||||
|
||||
@@ -125,7 +125,7 @@ class TorrentUDPTrackerTests: XCTestCase {
|
||||
|
||||
let peerId = "peerId12345678901234"
|
||||
let exampleEvent = TorrentTrackerEvent.started
|
||||
let examplePort = 789
|
||||
let examplePort: UInt16 = 789
|
||||
let expectedInfoHash = Data(bytes: [ 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0 ])
|
||||
let numberOfBytesRemaining = 456
|
||||
let numberOfBytesUploaded = 1234
|
||||
|
||||
Reference in New Issue
Block a user