diff --git a/BitTorrent.xcodeproj/project.pbxproj b/BitTorrent.xcodeproj/project.pbxproj index 609fd7c..a28b9fa 100644 --- a/BitTorrent.xcodeproj/project.pbxproj +++ b/BitTorrent.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 0434EAB818A3C318E433338A /* Pods_BitTorrentTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8680B0EA21B5A20E03DEE6D /* Pods_BitTorrentTests.framework */; }; 34D4082D0E0A2C899857DC89 /* Pods_BitTorrent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B47AE52A67ECE61259FCDAD /* Pods_BitTorrent.framework */; }; 867491B4C409A91D4D72CCE7 /* Pods_BitTorrent_BitTorrentExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D97F9FB9F74204574FF6840B /* Pods_BitTorrent_BitTorrentExample.framework */; }; + B501A5581F557E9E00B87911 /* RandomEnumeration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B501A5571F557E9E00B87911 /* RandomEnumeration.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 */; }; @@ -168,6 +169,7 @@ 8B47AE52A67ECE61259FCDAD /* Pods_BitTorrent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitTorrent.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 99A91AD4708288B297A8B700 /* Pods-BitTorrentTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitTorrentTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BitTorrentTests/Pods-BitTorrentTests.debug.xcconfig"; sourceTree = ""; }; A8680B0EA21B5A20E03DEE6D /* Pods_BitTorrentTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitTorrentTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B501A5571F557E9E00B87911 /* RandomEnumeration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomEnumeration.swift; sourceTree = ""; }; B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnectionTests.swift; sourceTree = ""; }; B50B24F81F0A554A00C23E7C /* UDPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnection.swift; sourceTree = ""; }; B514DD7A1F40971D00C932F8 /* TorrentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentViewController.swift; sourceTree = ""; }; @@ -407,6 +409,7 @@ B514DDA31F40E96100C932F8 /* CombinedNetworkSpeedTracker.swift */, B514DD8E1F40C53C00C932F8 /* NetworkSpeedTrackerTests.swift */, B514DD9B1F40DD1B00C932F8 /* URL.swift */, + B501A5571F557E9E00B87911 /* RandomEnumeration.swift */, ); path = Utilities; sourceTree = ""; @@ -982,6 +985,7 @@ B558F4831F0A647D00438BB4 /* InternetProtocol.swift in Sources */, B527F62B1F1BD5FA001F06AF /* TorrentPieceDownloadBuffer.swift in Sources */, B5BFD9CC1F3FAF9A00CE0186 /* TorrentTrackerManager.swift in Sources */, + B501A5581F557E9E00B87911 /* RandomEnumeration.swift in Sources */, B54D0C7A1CA69FAD004343BD /* TorrentMetaInfo.swift in Sources */, B5C06F131F12CDD8005730B3 /* TorrentPeer.swift in Sources */, B5F81E4B1F04399800B25C70 /* TorrentTrackerResponse.swift in Sources */, diff --git a/BitTorrent/Models/BitField.swift b/BitTorrent/Models/BitField.swift index 026f8da..5f97b88 100644 --- a/BitTorrent/Models/BitField.swift +++ b/BitTorrent/Models/BitField.swift @@ -92,15 +92,24 @@ public struct BitField: Equatable { } } -extension BitField: Sequence { - public func makeIterator() -> AnyIterator<(index: Int, isSet: Bool)> { - var index = 0 - return AnyIterator { - guard index < self.size else { return nil } - defer { - index += 1 - } - return (index, self.isSet(at: index)) - } +extension BitField: Collection { + + public typealias Index = Array.Index + + public var startIndex: Index { + return value.startIndex + } + + public var endIndex: Index { + return value.endIndex + } + + public subscript(position: Index) -> (index: Int, isSet: Bool) { + precondition(indices.contains(position), "out of bounds") + return (index: position, isSet: value[position]) + } + + public func index(after i: Index) -> Index { + return value.index(after: i) } } diff --git a/BitTorrent/Peer/Piece Download Buffer/TorrentPieceDownloadBuffer.swift b/BitTorrent/Peer/Piece Download Buffer/TorrentPieceDownloadBuffer.swift index 3123874..8a578c0 100644 --- a/BitTorrent/Peer/Piece Download Buffer/TorrentPieceDownloadBuffer.swift +++ b/BitTorrent/Peer/Piece Download Buffer/TorrentPieceDownloadBuffer.swift @@ -73,6 +73,4 @@ class TorrentPieceDownloadBuffer { pendingRequests.remove(at: pendingIndex) } - - } diff --git a/BitTorrent/Progress Manager/TorrentProgressManager.swift b/BitTorrent/Progress Manager/TorrentProgressManager.swift index ce30341..0f8dc8d 100644 --- a/BitTorrent/Progress Manager/TorrentProgressManager.swift +++ b/BitTorrent/Progress Manager/TorrentProgressManager.swift @@ -43,7 +43,7 @@ class TorrentProgressManager { } func getNextPieceToDownload(from availablePieces: BitField) -> TorrentPieceRequest? { - for (i, isSet) in availablePieces where isSet { + for (i, isSet) in availablePieces.pseudoRandomized where isSet { if !progress.hasPiece(i) && !progress.isCurrentlyDownloading(piece: i) { progress.setCurrentlyDownloading(piece: i) return (i, metaInfo.info.lengthOfPiece(at: i), metaInfo.info.pieces[i]) diff --git a/BitTorrent/TCP Networking/GCDAsyncSocketStub.swift b/BitTorrent/TCP Networking/GCDAsyncSocketStub.swift index a33f530..6897e0f 100644 --- a/BitTorrent/TCP Networking/GCDAsyncSocketStub.swift +++ b/BitTorrent/TCP Networking/GCDAsyncSocketStub.swift @@ -12,10 +12,10 @@ import CocoaAsyncSocket class GCDAsyncSocketStub: GCDAsyncSocket { var connectToHostCalled = false - var connectToHostParameters: (host: String, port: UInt16)? - override func connect(toHost host: String, onPort port: UInt16) throws { + var connectToHostParameters: (host: String, port: UInt16, timeout: TimeInterval)? + override func connect(toHost host: String, onPort port: UInt16, withTimeout timeout: TimeInterval) throws { connectToHostCalled = true - connectToHostParameters = (host, port) + connectToHostParameters = (host, port, timeout) } var readDataCalled = false diff --git a/BitTorrent/TCP Networking/TCPConnection.swift b/BitTorrent/TCP Networking/TCPConnection.swift index df195b7..a64b4a3 100644 --- a/BitTorrent/TCP Networking/TCPConnection.swift +++ b/BitTorrent/TCP Networking/TCPConnection.swift @@ -61,7 +61,7 @@ class TCPConnection: NSObject, TCPConnectionProtocol { } func connect(to host: String, onPort port: UInt16) throws { - try socket.connect(toHost: host, onPort: port) + try socket.connect(toHost: host, onPort: port, withTimeout: 15) } func disconnect() { diff --git a/BitTorrent/Utilities/RandomEnumeration.swift b/BitTorrent/Utilities/RandomEnumeration.swift new file mode 100644 index 0000000..29fd81e --- /dev/null +++ b/BitTorrent/Utilities/RandomEnumeration.swift @@ -0,0 +1,38 @@ +// +// RandomEnumeration.swift +// BitTorrent +// +// Created by Ben Davis on 29/08/2017. +// Copyright © 2017 Ben Davis. All rights reserved. +// + +import Foundation + +struct PseudoRandomizedSequence: Sequence + where Col: Collection, Col.Element == Elem, Col.Index == Int, Col.IndexDistance == Int { + + fileprivate let orderedSequence: Col + + func makeIterator() -> AnyIterator { + + let length = orderedSequence.count + let seed = Int(arc4random()) % length + let increment = 13 // prime as the step + + var generatedNumber = seed + var count = 0 + + return AnyIterator { + guard count != length else { return nil } + count += 1 + generatedNumber = (generatedNumber + increment) % length + return self.orderedSequence[generatedNumber] + } + } +} + +extension Collection where Index == Int, IndexDistance == Int { + var pseudoRandomized: PseudoRandomizedSequence { + return PseudoRandomizedSequence(orderedSequence: self) + } +} diff --git a/BitTorrentExample/TorrentInfoRowData.swift b/BitTorrentExample/TorrentInfoRowData.swift index a5d3c41..bf4df50 100644 --- a/BitTorrentExample/TorrentInfoRowData.swift +++ b/BitTorrentExample/TorrentInfoRowData.swift @@ -42,6 +42,7 @@ enum TorrentInfoRowData: Int { func value(using client: TorrentClient) -> String { let speedSampleSize: TimeInterval = 5 + let etaSampleSize: TimeInterval = 30 switch self { @@ -73,8 +74,8 @@ enum TorrentInfoRowData: Int { return "????" case .eta: - let speed = client.downloadSpeedTracker.numberOfBytesDownloaded(over: speedSampleSize) - let speedPerSecond = Double(speed) / speedSampleSize + let speed = client.downloadSpeedTracker.numberOfBytesDownloaded(over: etaSampleSize) + let speedPerSecond = Double(speed) / etaSampleSize guard speed > 0 else { return "∞" } let remaining = client.progress.remaining * client.metaInfo.info.pieceLength diff --git a/BitTorrentExample/TorrentViewController.swift b/BitTorrentExample/TorrentViewController.swift index a2de7c8..9a75ce3 100644 --- a/BitTorrentExample/TorrentViewController.swift +++ b/BitTorrentExample/TorrentViewController.swift @@ -92,10 +92,10 @@ extension TorrentViewController: UITableViewDataSource { return startCell } else { let reCheckCell = UITableViewCell(style: .default, reuseIdentifier: nil) - startCell.textLabel?.text = "Start" - startCell.textLabel?.textAlignment = .center - startCell.textLabel?.textColor = .blue - return startCell + reCheckCell.textLabel?.text = "Start" + reCheckCell.textLabel?.textAlignment = .center + reCheckCell.textLabel?.textColor = .blue + return reCheckCell } } }