Parsing tracker response
This commit is contained in:
@@ -35,6 +35,8 @@
|
||||
B5E9B0DA1F02E6F800EF58E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0D91F02E6F800EF58E3 /* Assets.xcassets */; };
|
||||
B5E9B0DD1F02E6F800EF58E3 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0DB1F02E6F800EF58E3 /* LaunchScreen.storyboard */; };
|
||||
B5E9B0E51F02FAC600EF58E3 /* TestText.torrent in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0E31F02F9E700EF58E3 /* TestText.torrent */; };
|
||||
B5F81E491F0436D600B25C70 /* TorrentPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F81E481F0436D600B25C70 /* TorrentPeer.swift */; };
|
||||
B5F81E4B1F04399800B25C70 /* TorrentTrackerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F81E4A1F04399800B25C70 /* TorrentTrackerResponse.swift */; };
|
||||
F2A74A776590BB3F37116B02 /* Pods_BitTorrent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B95A5AC293A32B10DB4660E /* Pods_BitTorrent.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
@@ -145,6 +147,8 @@
|
||||
B5E9B0DE1F02E6F800EF58E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
B5E9B0E31F02F9E700EF58E3 /* TestText.torrent */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestText.torrent; sourceTree = "<group>"; };
|
||||
B5E9B0E41F02F9E700EF58E3 /* text.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = text.txt; sourceTree = "<group>"; };
|
||||
B5F81E481F0436D600B25C70 /* TorrentPeer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentPeer.swift; sourceTree = "<group>"; };
|
||||
B5F81E4A1F04399800B25C70 /* TorrentTrackerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TorrentTrackerResponse.swift; path = Tracker/TorrentTrackerResponse.swift; sourceTree = "<group>"; };
|
||||
BDA22A3943D47D6B2152CB15 /* Pods-BitTorrent.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitTorrent.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BitTorrent/Pods-BitTorrent.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
BFDACB202E071FF8247EC868 /* Pods-BitTorrentExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitTorrentExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BitTorrentExample/Pods-BitTorrentExample.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
C79B494173728B25AA5E2B62 /* Pods-BitTorrent-BitTorrentExample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitTorrent-BitTorrentExample.release.xcconfig"; path = "Pods/Target Support Files/Pods-BitTorrent-BitTorrentExample/Pods-BitTorrent-BitTorrentExample.release.xcconfig"; sourceTree = "<group>"; };
|
||||
@@ -269,6 +273,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B55317DB1F02FC4D00909ADF /* TorrentHTTPTracker.swift */,
|
||||
B5F81E4A1F04399800B25C70 /* TorrentTrackerResponse.swift */,
|
||||
B5BD7FD51F03032400621BC2 /* TorrentHTTPTrackerTests.swift */,
|
||||
);
|
||||
name = Tracker;
|
||||
@@ -304,6 +309,7 @@
|
||||
children = (
|
||||
B585AB771C3833450093FA41 /* BitTorrent.h */,
|
||||
B585AB831C3833450093FA41 /* BitTorrentTests.swift */,
|
||||
B5F81E461F0436CC00B25C70 /* Peer */,
|
||||
B55317DA1F02FC3000909ADF /* Tracker */,
|
||||
B54D0C251CA56A25004343BD /* Models */,
|
||||
B54D0C291CA5785F004343BD /* Utilities */,
|
||||
@@ -354,6 +360,14 @@
|
||||
path = "Single text file";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5F81E461F0436CC00B25C70 /* Peer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5F81E481F0436D600B25C70 /* TorrentPeer.swift */,
|
||||
);
|
||||
path = Peer;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@@ -702,9 +716,11 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5F81E491F0436D600B25C70 /* TorrentPeer.swift in Sources */,
|
||||
B55317DC1F02FC4D00909ADF /* TorrentHTTPTracker.swift in Sources */,
|
||||
B5E977961CAFB46B0038EBE7 /* String+URLEncode.swift in Sources */,
|
||||
B54D0C7A1CA69FAD004343BD /* TorrentMetaInfo.swift in Sources */,
|
||||
B5F81E4B1F04399800B25C70 /* TorrentTrackerResponse.swift in Sources */,
|
||||
B54D0C7B1CA69FD8004343BD /* Data+sha1.swift in Sources */,
|
||||
B5530DB21F03063300F71CCD /* HTTPConnection.swift in Sources */,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// TorrentPeer.swift
|
||||
// BitTorrent
|
||||
//
|
||||
// Created by Ben Davis on 28/06/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct TorrentPeerInfo {
|
||||
let ip: String
|
||||
let port: Int
|
||||
let peerId: Data?
|
||||
|
||||
init(ip: String, port: Int, peerId: Data?) {
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.peerId = peerId
|
||||
}
|
||||
|
||||
init?(dictionary: [String: Any]) {
|
||||
|
||||
guard let ipData = dictionary["ip"] as? Data,
|
||||
let ip = String(asciiData: ipData),
|
||||
let port = dictionary["port"] as? Int else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.ip = ip
|
||||
self.port = port
|
||||
self.peerId = dictionary["peer id"] as? Data
|
||||
}
|
||||
|
||||
static func peersInfoFromBinaryModel(_ data: Data) -> [TorrentPeerInfo] {
|
||||
let numberOfPeers = data.count / 6
|
||||
var result: [TorrentPeerInfo] = []
|
||||
for i in 0..<numberOfPeers {
|
||||
let ip1 = Int(data[i*6])
|
||||
let ip2 = Int(data[i*6 + 1])
|
||||
let ip3 = Int(data[i*6 + 2])
|
||||
let ip4 = Int(data[i*6 + 3])
|
||||
let portBytes = [data[i*6 + 5], data[i*6 + 4]]
|
||||
|
||||
let portu16 = UnsafePointer(portBytes).withMemoryRebound(to: UInt16.self, capacity: 1) {
|
||||
$0.pointee
|
||||
}
|
||||
let port = Int(portu16)
|
||||
|
||||
print("Found peer: \(ip1).\(ip2).\(ip3).\(ip4):\(port)")
|
||||
|
||||
let peer = TorrentPeerInfo(ip: "\(ip1).\(ip2).\(ip3).\(ip4)", port: port, peerId: nil)
|
||||
result.append(peer)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,75 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
//tracker.announceClient(self.peerId,
|
||||
// port: 6881,
|
||||
// numberOfBytes: self.metaInfo.length,
|
||||
// infoHash: self.metaInfo.infoHash,
|
||||
// numwant: 20,
|
||||
// key: "-BD0000-bxa]N#IRKqv`");
|
||||
protocol TorrentTrackerDelegate: class {
|
||||
func torrentTracker(_ sender: TorrentHTTPTracker, receivedResponse: TorrentHTTPTrackerResponse)
|
||||
func torrentTracker(_ sender: TorrentHTTPTracker, receivedErrorMessage: String)
|
||||
}
|
||||
|
||||
enum TorrentHTTPTrackerEvent {
|
||||
case started, stopped, completed
|
||||
|
||||
var name: String {
|
||||
switch self {
|
||||
case .started:
|
||||
return "started"
|
||||
case .stopped:
|
||||
return "stopped"
|
||||
case .completed:
|
||||
return "completed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TorrentHTTPTracker {
|
||||
|
||||
let metaInfo: TorrentMetaInfo
|
||||
let connection: BasicHTTPConnection
|
||||
|
||||
weak var delegate: TorrentTrackerDelegate?
|
||||
|
||||
init(metaInfo: TorrentMetaInfo, connection: BasicHTTPConnection = HTTPConnection()) {
|
||||
self.metaInfo = metaInfo
|
||||
self.connection = connection
|
||||
}
|
||||
|
||||
func announceClient(with peerId: String,
|
||||
port: Int,
|
||||
event: TorrentHTTPTrackerEvent = .started,
|
||||
infoHash: Data,
|
||||
numberOfBytesRemaining: Int,
|
||||
numberOfBytesUploaded: Int,
|
||||
numberOfBytesDownloaded: Int,
|
||||
numberOfPeersToFetch: Int) {
|
||||
|
||||
let urlParameters = [
|
||||
"info_hash" : String(urlEncodingData: metaInfo.infoHash),
|
||||
"peer_id" : "\(peerId)",
|
||||
"port" : "\(port)",
|
||||
"uploaded" : "\(numberOfBytesUploaded)",
|
||||
"downloaded" : "\(numberOfBytesDownloaded)",
|
||||
"left" : "\(numberOfBytesRemaining)",
|
||||
"compact" : "1",
|
||||
"event" : event.name,
|
||||
"numwant" : "\(numberOfPeersToFetch)"
|
||||
]
|
||||
|
||||
connection.makeRequest(url: metaInfo.announce, urlParameters: urlParameters) { [weak self] response in
|
||||
|
||||
guard self != nil else {
|
||||
return
|
||||
}
|
||||
|
||||
if let data = response.responseData {
|
||||
if let result = TorrentHTTPTrackerResponse(data: data) {
|
||||
self!.delegate?.torrentTracker(self!, receivedResponse: result)
|
||||
} else if let errorMessage = TorrentHTTPTrackerResponse.errorMessage(fromResponseData: data) {
|
||||
self!.delegate?.torrentTracker(self!, receivedErrorMessage: errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//func makePeerId() -> String {
|
||||
// var peerId = "-BD0000-"
|
||||
@@ -30,43 +93,3 @@ import Foundation
|
||||
//
|
||||
// return peerId
|
||||
//}
|
||||
|
||||
class TorrentHTTPTracker {
|
||||
|
||||
let metaInfo: TorrentMetaInfo
|
||||
let connection: BasicHTTPConnection
|
||||
|
||||
init(metaInfo: TorrentMetaInfo, connection: BasicHTTPConnection = HTTPConnection()) {
|
||||
self.metaInfo = metaInfo
|
||||
self.connection = connection
|
||||
}
|
||||
|
||||
func announceClient(with peerId: String,
|
||||
port: Int,
|
||||
numberOfBytesRemaining: Int,
|
||||
infoHash: Data,
|
||||
numberOfPeersToFetch: Int,
|
||||
peerKey: String) {
|
||||
|
||||
let urlParameters = [
|
||||
"info_hash" : String(urlEncodingData: metaInfo.infoHash),
|
||||
"peer_id" : "\(peerId)",
|
||||
"port" : "\(port)",
|
||||
"uploaded" : "0",
|
||||
"downloaded" : "0",
|
||||
"left" : "\(numberOfBytesRemaining)",
|
||||
"compact" : "1",
|
||||
"event" : "started",
|
||||
"numwant" : "\(numberOfPeersToFetch)",
|
||||
"key" : peerKey,
|
||||
]
|
||||
|
||||
connection.makeRequest(url: metaInfo.announce, urlParameters: urlParameters) { response in
|
||||
|
||||
if let data = response.responseData, let utf8Text = String(data: data, encoding: .utf8) {
|
||||
print("Data: \(utf8Text)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,11 +9,46 @@
|
||||
import XCTest
|
||||
@testable import BitTorrent
|
||||
|
||||
class TorrentTrackerDelegateSpy: TorrentTrackerDelegate {
|
||||
|
||||
var receivedResponseCalled = false
|
||||
var receivedResponseParameter: TorrentHTTPTrackerResponse? = nil
|
||||
|
||||
var receivedErrorMessageCalled = false
|
||||
var receivedErrorMessageParameter: String? = nil
|
||||
|
||||
func torrentTracker(_ sender: TorrentHTTPTracker, receivedResponse response: TorrentHTTPTrackerResponse) {
|
||||
receivedResponseCalled = true
|
||||
receivedResponseParameter = response
|
||||
}
|
||||
|
||||
func torrentTracker(_ sender: TorrentHTTPTracker, receivedErrorMessage errorMessage: String) {
|
||||
receivedErrorMessageCalled = true
|
||||
receivedErrorMessageParameter = errorMessage
|
||||
}
|
||||
}
|
||||
|
||||
class TorrentHTTPTrackerTests: XCTestCase {
|
||||
|
||||
var connectionStub: HTTPConnectionStub!
|
||||
var sut: TorrentHTTPTracker!
|
||||
|
||||
var delegateSpy: TorrentTrackerDelegateSpy!
|
||||
|
||||
let expectedURLParameters: [String: String] = [
|
||||
"info_hash": "%F0%B8q%98%99S%97%3F%BF%A9M%C8%14%98%EE%8D%20%5B%B2%23",
|
||||
"peer_id" : "peerId",
|
||||
"port" : "123",
|
||||
"uploaded" : "1234",
|
||||
"downloaded" : "4321",
|
||||
"left" : "456",
|
||||
"compact" : "1",
|
||||
"event" : "started",
|
||||
"numwant" : "321",
|
||||
]
|
||||
|
||||
let basicResponseData = "d8:completei1e10:incompletei2e8:intervali600e5:peers0:e".data(using: .ascii)!
|
||||
|
||||
override func setUp() {
|
||||
super.setUp()
|
||||
|
||||
@@ -23,34 +58,162 @@ class TorrentHTTPTrackerTests: XCTestCase {
|
||||
|
||||
connectionStub = HTTPConnectionStub()
|
||||
sut = TorrentHTTPTracker(metaInfo: metaInfo, connection: connectionStub)
|
||||
|
||||
delegateSpy = TorrentTrackerDelegateSpy()
|
||||
sut.delegate = delegateSpy
|
||||
}
|
||||
|
||||
func performAnnounce(withEvent event: TorrentHTTPTrackerEvent) {
|
||||
sut.announceClient(with: "peerId",
|
||||
port: 123,
|
||||
event: event,
|
||||
infoHash: Data(bytes: [7,8,9]),
|
||||
numberOfBytesRemaining: 456,
|
||||
numberOfBytesUploaded: 1234,
|
||||
numberOfBytesDownloaded: 4321,
|
||||
numberOfPeersToFetch: 321)
|
||||
}
|
||||
|
||||
func test_announce() {
|
||||
|
||||
sut.announceClient(with: "peerId",
|
||||
port: 123,
|
||||
numberOfBytesRemaining: 456,
|
||||
infoHash: Data(bytes: [7,8,9]),
|
||||
numberOfPeersToFetch: 321,
|
||||
peerKey: "key")
|
||||
|
||||
performAnnounce(withEvent: .started)
|
||||
let request = connectionStub.lastRequest
|
||||
|
||||
let expectedURLParameters = [
|
||||
"info_hash": "%F0%B8q%98%99S%97%3F%BF%A9M%C8%14%98%EE%8D%20%5B%B2%23",
|
||||
"peer_id" : "peerId",
|
||||
"port" : "123",
|
||||
"uploaded" : "0",
|
||||
"downloaded" : "0",
|
||||
"left" : "456",
|
||||
"compact" : "1",
|
||||
"event" : "started",
|
||||
"numwant" : "321",
|
||||
"key" : "key",
|
||||
]
|
||||
|
||||
XCTAssertEqual(request.url.absoluteString, "http://127.0.0.1:53420/announce")
|
||||
XCTAssertEqual(request.urlParameters!, expectedURLParameters)
|
||||
}
|
||||
|
||||
func test_sendStoppedEvent() {
|
||||
performAnnounce(withEvent: .stopped)
|
||||
let request = connectionStub.lastRequest
|
||||
XCTAssertEqual(request.urlParameters!["event"], "stopped")
|
||||
}
|
||||
|
||||
func test_sendCompletedEvent() {
|
||||
performAnnounce(withEvent: .completed)
|
||||
let request = connectionStub.lastRequest
|
||||
XCTAssertEqual(request.urlParameters!["event"], "completed")
|
||||
}
|
||||
|
||||
func test_delegateNotifiedOnTrackerResponse() {
|
||||
|
||||
performAnnounce(withEvent: .started)
|
||||
|
||||
connectionStub.completeLastRequest(with: HTTPResponse(completed: true,
|
||||
responseData: basicResponseData,
|
||||
statusCode: 200))
|
||||
|
||||
XCTAssert(delegateSpy.receivedResponseCalled)
|
||||
}
|
||||
|
||||
func test_basicResponseParsing() {
|
||||
|
||||
performAnnounce(withEvent: .started)
|
||||
|
||||
connectionStub.completeLastRequest(with: HTTPResponse(completed: true,
|
||||
responseData: basicResponseData,
|
||||
statusCode: 200))
|
||||
|
||||
let response = delegateSpy.receivedResponseParameter!
|
||||
|
||||
XCTAssertEqual(response.numberOfPeersComplete, 1)
|
||||
XCTAssertEqual(response.numberOfPeersIncomplete, 2)
|
||||
XCTAssertNil(response.trackerId)
|
||||
XCTAssertEqual(response.interval, 600)
|
||||
XCTAssertEqual(response.minimumInterval, 0)
|
||||
XCTAssertNil(response.warning)
|
||||
}
|
||||
|
||||
func test_optionalResponseFieldsParsing() {
|
||||
performAnnounce(withEvent: .started)
|
||||
|
||||
let completeResponse = "d15:warning message7:warning10:tracker id9:trackerId12:min intervali60e8:completei1e10:incompletei2e8:intervali600e5:peers0:e".data(using: .ascii)!
|
||||
|
||||
connectionStub.completeLastRequest(with: HTTPResponse(completed: true,
|
||||
responseData: completeResponse,
|
||||
statusCode: 200))
|
||||
|
||||
let response = delegateSpy.receivedResponseParameter!
|
||||
|
||||
XCTAssertEqual(response.trackerId, "trackerId".data(using: .ascii))
|
||||
XCTAssertEqual(response.minimumInterval, 60)
|
||||
XCTAssertEqual(response.warning, "warning")
|
||||
}
|
||||
|
||||
func test_binaryPeersFormat() {
|
||||
performAnnounce(withEvent: .started)
|
||||
|
||||
let peersBinary = Data(bytes: [0x7f, 0x00, 0x00, 0x01, 0x3c, 0x17, 0x7f, 0x00, 0x00, 0x01, 0x1a, 0xe1])
|
||||
|
||||
let responseData = "d8:completei1e10:incompletei2e8:intervali600e5:peers12:".data(using: .ascii)! +
|
||||
peersBinary +
|
||||
"e".data(using: .ascii)!
|
||||
|
||||
connectionStub.completeLastRequest(with: HTTPResponse(completed: true,
|
||||
responseData: responseData,
|
||||
statusCode: 200))
|
||||
|
||||
let response = delegateSpy.receivedResponseParameter!
|
||||
|
||||
XCTAssertEqual(response.peers.count, 2)
|
||||
XCTAssertEqual(response.peers.first!.ip, "127.0.0.1")
|
||||
XCTAssertEqual(response.peers.first!.port, 15383)
|
||||
XCTAssertEqual(response.peers.last!.ip, "127.0.0.1")
|
||||
XCTAssertEqual(response.peers.last!.port, 6881)
|
||||
}
|
||||
|
||||
func test_dictionaryPeersFormat() {
|
||||
performAnnounce(withEvent: .started)
|
||||
|
||||
let peer1Id = "peerId1-------------"
|
||||
let peer1IP = "127.0.0.1"
|
||||
let peer1Port = 15383
|
||||
let peer2Id = "peerId2-------------"
|
||||
let peer2IP = "127.0.0.1"
|
||||
let peer2Port = 6881
|
||||
|
||||
let peer1 = "d7:peer id20:\(peer1Id)2:ip9:\(peer1IP)4:porti\(peer1Port)ee"
|
||||
let peer2 = "d7:peer id20:\(peer2Id)2:ip9:\(peer2IP)4:porti\(peer2Port)ee"
|
||||
|
||||
let responseData = "d8:completei1e10:incompletei2e8:intervali600e5:peersl\(peer1)\(peer2)ee".data(using: .ascii)!
|
||||
|
||||
connectionStub.completeLastRequest(with: HTTPResponse(completed: true,
|
||||
responseData: responseData,
|
||||
statusCode: 200))
|
||||
|
||||
let response = delegateSpy.receivedResponseParameter!
|
||||
|
||||
XCTAssertEqual(response.peers.count, 2)
|
||||
|
||||
XCTAssertEqual(response.peers.first!.peerId!, peer1Id.data(using: .ascii))
|
||||
XCTAssertEqual(response.peers.first!.ip, peer1IP)
|
||||
XCTAssertEqual(response.peers.first!.port, peer1Port)
|
||||
|
||||
XCTAssertEqual(response.peers.last!.peerId!, peer2Id.data(using: .ascii))
|
||||
XCTAssertEqual(response.peers.last!.ip, peer2IP)
|
||||
XCTAssertEqual(response.peers.last!.port, peer2Port)
|
||||
}
|
||||
|
||||
func test_failResponseDoesNotCallDelegate() {
|
||||
performAnnounce(withEvent: .started)
|
||||
|
||||
let responseData = "d14:failure reason45:invalid info_hash (not 20 chars):123length: 3e".data(using: .ascii)
|
||||
|
||||
connectionStub.completeLastRequest(with: HTTPResponse(completed: true,
|
||||
responseData: responseData,
|
||||
statusCode: 200))
|
||||
|
||||
XCTAssertFalse(delegateSpy.receivedResponseCalled)
|
||||
XCTAssert(delegateSpy.receivedErrorMessageCalled)
|
||||
XCTAssertEqual(delegateSpy.receivedErrorMessageParameter, "invalid info_hash (not 20 chars):123length: 3")
|
||||
}
|
||||
|
||||
func test_invalidTrackerResponse() {
|
||||
performAnnounce(withEvent: .started)
|
||||
|
||||
connectionStub.completeLastRequest(with: HTTPResponse(completed: true,
|
||||
responseData: Data(),
|
||||
statusCode: 500))
|
||||
|
||||
XCTAssertFalse(delegateSpy.receivedResponseCalled)
|
||||
XCTAssertFalse(delegateSpy.receivedErrorMessageCalled)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
//
|
||||
// TorrentTrackerResponse.swift
|
||||
// BitTorrent
|
||||
//
|
||||
// Created by Ben Davis on 28/06/2017.
|
||||
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import BEncode
|
||||
|
||||
struct TorrentHTTPTrackerResponse {
|
||||
|
||||
let peers: [TorrentPeerInfo]
|
||||
let numberOfPeersComplete: Int // Seeders
|
||||
let numberOfPeersIncomplete: Int // Leechers
|
||||
|
||||
let trackerId: Data?
|
||||
|
||||
let interval: Int
|
||||
let minimumInterval: Int
|
||||
|
||||
let warning: String?
|
||||
|
||||
init(peers: [TorrentPeerInfo],
|
||||
numberOfPeersComplete: Int = 0,
|
||||
numberOfPeersIncomplete: Int = 0,
|
||||
trackerId: Data? = nil,
|
||||
interval: Int = 60,
|
||||
minimumInterval: Int = 0,
|
||||
warning: String? = nil) {
|
||||
|
||||
self.peers = peers
|
||||
self.numberOfPeersComplete = numberOfPeersComplete
|
||||
self.numberOfPeersIncomplete = numberOfPeersIncomplete
|
||||
self.trackerId = trackerId
|
||||
self.interval = interval
|
||||
self.minimumInterval = minimumInterval
|
||||
self.warning = warning
|
||||
}
|
||||
}
|
||||
|
||||
extension TorrentHTTPTrackerResponse {
|
||||
|
||||
init?(data: Data) {
|
||||
let bencode: [String: Any]
|
||||
do {
|
||||
bencode = try BEncoder.decodeStringKeyedDictionary(data)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
|
||||
guard let numberOfPeersComplete = bencode["complete"] as? Int,
|
||||
let numberOfPeersIncomplete = bencode["incomplete"] as? Int,
|
||||
let interval = bencode["interval"] as? Int,
|
||||
let peersObject = bencode["peers"] else {
|
||||
return nil
|
||||
}
|
||||
|
||||
if let binaryData = peersObject as? Data {
|
||||
self.peers = TorrentPeerInfo.peersInfoFromBinaryModel(binaryData)
|
||||
} else {
|
||||
guard let peersDictionaries = peersObject as? [[String: Any]] else {
|
||||
return nil
|
||||
}
|
||||
self.peers = peersDictionaries.map(TorrentPeerInfo.init(dictionary:)).flatMap({ $0 })
|
||||
}
|
||||
self.numberOfPeersComplete = numberOfPeersComplete
|
||||
self.numberOfPeersIncomplete = numberOfPeersIncomplete
|
||||
self.trackerId = bencode["tracker id"] as? Data
|
||||
self.interval = interval
|
||||
self.minimumInterval = bencode["min interval"] as? Int ?? 0
|
||||
|
||||
if let warningData = bencode["warning message"] as? Data {
|
||||
self.warning = String(data: warningData, encoding: .utf8)
|
||||
} else {
|
||||
self.warning = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TorrentHTTPTrackerResponse {
|
||||
|
||||
static func errorMessage(fromResponseData data: Data) -> String? {
|
||||
|
||||
let bencode: [String: Any]
|
||||
do {
|
||||
bencode = try BEncoder.decodeStringKeyedDictionary(data)
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
|
||||
return String(asciiData: bencode["failure reason"] as? Data)
|
||||
}
|
||||
}
|
||||
@@ -31,11 +31,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
tracker.announceClient(with: "-BD0000-bxa]N#IRKqv`",
|
||||
port: 6881,
|
||||
numberOfBytesRemaining: 117,
|
||||
infoHash: Data(bytes:[ 0xf0, 0xb8, 0x71, 0x98, 0x99, 0x53, 0x97, 0x3f, 0xbf, 0xa9,
|
||||
0x4d, 0xc8, 0x14, 0x98, 0xee, 0x8d, 0x20, 0x5b, 0xb2, 0x23]),
|
||||
numberOfPeersToFetch: 50,
|
||||
peerKey: "key")
|
||||
event: .started,
|
||||
infoHash: metaInfo.infoHash,
|
||||
numberOfBytesRemaining: metaInfo.info.length,
|
||||
numberOfBytesUploaded: 0,
|
||||
numberOfBytesDownloaded: 0,
|
||||
numberOfPeersToFetch: 50)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -84,6 +84,11 @@
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ios-marketing",
|
||||
"size" : "1024x1024",
|
||||
"scale" : "1x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
|
||||
Reference in New Issue
Block a user