Torrent UDP Tracker
This commit is contained in:
@@ -12,6 +12,8 @@
|
|||||||
867491B4C409A91D4D72CCE7 /* Pods_BitTorrent_BitTorrentExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D97F9FB9F74204574FF6840B /* Pods_BitTorrent_BitTorrentExample.framework */; };
|
867491B4C409A91D4D72CCE7 /* Pods_BitTorrent_BitTorrentExample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D97F9FB9F74204574FF6840B /* Pods_BitTorrent_BitTorrentExample.framework */; };
|
||||||
B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */; };
|
B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */; };
|
||||||
B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F81F0A554A00C23E7C /* UDPConnection.swift */; };
|
B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F81F0A554A00C23E7C /* UDPConnection.swift */; };
|
||||||
|
B51D6C091F0C180600E1E3AB /* TorrentUDPTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51D6C031F0C17AE00E1E3AB /* TorrentUDPTrackerTests.swift */; };
|
||||||
|
B51D6C0A1F0C180D00E1E3AB /* TorrentUDPTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51D6C061F0C17C000E1E3AB /* TorrentUDPTracker.swift */; };
|
||||||
B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B537CF051F03148B0084089B /* HTTPConnectionStub.swift */; };
|
B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B537CF051F03148B0084089B /* HTTPConnectionStub.swift */; };
|
||||||
B537CF461F031AD20084089B /* TestText.torrent in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0E31F02F9E700EF58E3 /* TestText.torrent */; };
|
B537CF461F031AD20084089B /* TestText.torrent in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0E31F02F9E700EF58E3 /* TestText.torrent */; };
|
||||||
B537CF491F031C3D0084089B /* BEncode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B54D0C1A1CA53983004343BD /* BEncode.framework */; };
|
B537CF491F031C3D0084089B /* BEncode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B54D0C1A1CA53983004343BD /* BEncode.framework */; };
|
||||||
@@ -34,6 +36,7 @@
|
|||||||
B585AB781C3833450093FA41 /* BitTorrent.h in Headers */ = {isa = PBXBuildFile; fileRef = B585AB771C3833450093FA41 /* BitTorrent.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
B585AB781C3833450093FA41 /* BitTorrent.h in Headers */ = {isa = PBXBuildFile; fileRef = B585AB771C3833450093FA41 /* BitTorrent.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
B585AB7F1C3833450093FA41 /* BitTorrent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B585AB741C3833450093FA41 /* BitTorrent.framework */; };
|
B585AB7F1C3833450093FA41 /* BitTorrent.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B585AB741C3833450093FA41 /* BitTorrent.framework */; };
|
||||||
B585AB841C3833450093FA41 /* BitTorrentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B585AB831C3833450093FA41 /* BitTorrentTests.swift */; };
|
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 */; };
|
B5BD7FD61F03032400621BC2 /* TorrentHTTPTrackerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5BD7FD51F03032400621BC2 /* TorrentHTTPTrackerTests.swift */; };
|
||||||
B5E977961CAFB46B0038EBE7 /* String+URLEncode.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E977951CAFB46B0038EBE7 /* String+URLEncode.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 */; };
|
B5E9B0D81F02E6F800EF58E3 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E9B0D71F02E6F800EF58E3 /* AppDelegate.swift */; };
|
||||||
@@ -119,6 +122,8 @@
|
|||||||
A8680B0EA21B5A20E03DEE6D /* Pods_BitTorrentTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitTorrentTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
A8680B0EA21B5A20E03DEE6D /* Pods_BitTorrentTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitTorrentTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnectionTests.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>"; };
|
B50B24F81F0A554A00C23E7C /* UDPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnection.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>"; };
|
||||||
B537CF051F03148B0084089B /* HTTPConnectionStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnectionStub.swift; path = "HTTP Networking/HTTPConnectionStub.swift"; sourceTree = "<group>"; };
|
B537CF051F03148B0084089B /* HTTPConnectionStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnectionStub.swift; path = "HTTP Networking/HTTPConnectionStub.swift"; sourceTree = "<group>"; };
|
||||||
B54D0C141CA53983004343BD /* BEncode.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BEncode.xcodeproj; path = Submodules/BEncodeSwift/BEncode.xcodeproj; sourceTree = "<group>"; };
|
B54D0C141CA53983004343BD /* BEncode.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BEncode.xcodeproj; path = Submodules/BEncodeSwift/BEncode.xcodeproj; sourceTree = "<group>"; };
|
||||||
B54D0C231CA56A22004343BD /* TorrentMetaInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TorrentMetaInfo.swift; path = Models/TorrentMetaInfo.swift; sourceTree = "<group>"; };
|
B54D0C231CA56A22004343BD /* TorrentMetaInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TorrentMetaInfo.swift; path = Models/TorrentMetaInfo.swift; sourceTree = "<group>"; };
|
||||||
@@ -133,7 +138,7 @@
|
|||||||
B551C9541F0B9D3D004115CB /* GCDAsyncUdpSocketStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GCDAsyncUdpSocketStub.swift; sourceTree = "<group>"; };
|
B551C9541F0B9D3D004115CB /* GCDAsyncUdpSocketStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GCDAsyncUdpSocketStub.swift; sourceTree = "<group>"; };
|
||||||
B5530DB11F03063300F71CCD /* HTTPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnection.swift; path = "HTTP Networking/HTTPConnection.swift"; sourceTree = "<group>"; };
|
B5530DB11F03063300F71CCD /* HTTPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnection.swift; path = "HTTP Networking/HTTPConnection.swift"; sourceTree = "<group>"; };
|
||||||
B5530DB41F03063E00F71CCD /* HTTPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnectionTests.swift; path = "HTTP Networking/HTTPConnectionTests.swift"; sourceTree = "<group>"; };
|
B5530DB41F03063E00F71CCD /* HTTPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnectionTests.swift; path = "HTTP Networking/HTTPConnectionTests.swift"; sourceTree = "<group>"; };
|
||||||
B55317DB1F02FC4D00909ADF /* TorrentHTTPTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TorrentHTTPTracker.swift; path = Tracker/TorrentHTTPTracker.swift; sourceTree = "<group>"; };
|
B55317DB1F02FC4D00909ADF /* TorrentHTTPTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentHTTPTracker.swift; sourceTree = "<group>"; };
|
||||||
B55317DF1F02FE1500909ADF /* URLEncodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLEncodeTests.swift; path = "HTTP Networking/URLEncodeTests.swift"; sourceTree = "<group>"; };
|
B55317DF1F02FE1500909ADF /* URLEncodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLEncodeTests.swift; path = "HTTP Networking/URLEncodeTests.swift"; sourceTree = "<group>"; };
|
||||||
B558F4821F0A647D00438BB4 /* InternetProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetProtocol.swift; sourceTree = "<group>"; };
|
B558F4821F0A647D00438BB4 /* InternetProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetProtocol.swift; sourceTree = "<group>"; };
|
||||||
B558F4841F0A73D000438BB4 /* InternetProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetProtocolTests.swift; sourceTree = "<group>"; };
|
B558F4841F0A73D000438BB4 /* InternetProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetProtocolTests.swift; sourceTree = "<group>"; };
|
||||||
@@ -144,7 +149,8 @@
|
|||||||
B585AB7E1C3833450093FA41 /* BitTorrentTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitTorrentTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
B585AB7E1C3833450093FA41 /* BitTorrentTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitTorrentTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
B585AB831C3833450093FA41 /* BitTorrentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BitTorrentTests.swift; path = BitTorrentTests/BitTorrentTests.swift; sourceTree = SOURCE_ROOT; };
|
B585AB831C3833450093FA41 /* BitTorrentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = BitTorrentTests.swift; path = BitTorrentTests/BitTorrentTests.swift; sourceTree = SOURCE_ROOT; };
|
||||||
B585AB851C3833450093FA41 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
B585AB851C3833450093FA41 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
B5BD7FD51F03032400621BC2 /* TorrentHTTPTrackerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TorrentHTTPTrackerTests.swift; path = Tracker/TorrentHTTPTrackerTests.swift; 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>"; };
|
||||||
B5E977951CAFB46B0038EBE7 /* String+URLEncode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "String+URLEncode.swift"; path = "HTTP Networking/String+URLEncode.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; };
|
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>"; };
|
B5E9B0D71F02E6F800EF58E3 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
@@ -154,7 +160,7 @@
|
|||||||
B5E9B0E31F02F9E700EF58E3 /* TestText.torrent */ = {isa = PBXFileReference; lastKnownFileType = file; path = TestText.torrent; 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>"; };
|
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>"; };
|
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>"; };
|
B5F81E4A1F04399800B25C70 /* TorrentTrackerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TorrentTrackerResponse.swift; sourceTree = "<group>"; };
|
||||||
D437CE230D6B283EED6C1A3E /* 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>"; };
|
D437CE230D6B283EED6C1A3E /* 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>"; };
|
||||||
D97F9FB9F74204574FF6840B /* Pods_BitTorrent_BitTorrentExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitTorrent_BitTorrentExample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
D97F9FB9F74204574FF6840B /* Pods_BitTorrent_BitTorrentExample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BitTorrent_BitTorrentExample.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
EDC27D7221FFFF5E25D0A77F /* Pods-BitTorrent-BitTorrentExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitTorrent-BitTorrentExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BitTorrent-BitTorrentExample/Pods-BitTorrent-BitTorrentExample.debug.xcconfig"; sourceTree = "<group>"; };
|
EDC27D7221FFFF5E25D0A77F /* Pods-BitTorrent-BitTorrentExample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-BitTorrent-BitTorrentExample.debug.xcconfig"; path = "Pods/Target Support Files/Pods-BitTorrent-BitTorrentExample/Pods-BitTorrent-BitTorrentExample.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
@@ -286,11 +292,13 @@
|
|||||||
B55317DA1F02FC3000909ADF /* Tracker */ = {
|
B55317DA1F02FC3000909ADF /* Tracker */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
B55317DB1F02FC4D00909ADF /* TorrentHTTPTracker.swift */,
|
|
||||||
B5F81E4A1F04399800B25C70 /* TorrentTrackerResponse.swift */,
|
B5F81E4A1F04399800B25C70 /* TorrentTrackerResponse.swift */,
|
||||||
|
B55317DB1F02FC4D00909ADF /* TorrentHTTPTracker.swift */,
|
||||||
B5BD7FD51F03032400621BC2 /* TorrentHTTPTrackerTests.swift */,
|
B5BD7FD51F03032400621BC2 /* TorrentHTTPTrackerTests.swift */,
|
||||||
|
B51D6C061F0C17C000E1E3AB /* TorrentUDPTracker.swift */,
|
||||||
|
B51D6C031F0C17AE00E1E3AB /* TorrentUDPTrackerTests.swift */,
|
||||||
);
|
);
|
||||||
name = Tracker;
|
path = Tracker;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
B585AB6A1C3833450093FA41 = {
|
B585AB6A1C3833450093FA41 = {
|
||||||
@@ -339,6 +347,7 @@
|
|||||||
children = (
|
children = (
|
||||||
B585AB851C3833450093FA41 /* Info.plist */,
|
B585AB851C3833450093FA41 /* Info.plist */,
|
||||||
B56A8A061C83539300426AC8 /* TestHelpers.swift */,
|
B56A8A061C83539300426AC8 /* TestHelpers.swift */,
|
||||||
|
B59E1B261F0E6E5F007753CE /* BitTorrentTestMacros.swift */,
|
||||||
);
|
);
|
||||||
path = BitTorrentTests;
|
path = BitTorrentTests;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -741,6 +750,7 @@
|
|||||||
B54D0C7B1CA69FD8004343BD /* Data+sha1.swift in Sources */,
|
B54D0C7B1CA69FD8004343BD /* Data+sha1.swift in Sources */,
|
||||||
B5530DB21F03063300F71CCD /* HTTPConnection.swift in Sources */,
|
B5530DB21F03063300F71CCD /* HTTPConnection.swift in Sources */,
|
||||||
B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */,
|
B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */,
|
||||||
|
B51D6C0A1F0C180D00E1E3AB /* TorrentUDPTracker.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -748,6 +758,7 @@
|
|||||||
isa = PBXSourcesBuildPhase;
|
isa = PBXSourcesBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
|
B51D6C091F0C180600E1E3AB /* TorrentUDPTrackerTests.swift in Sources */,
|
||||||
B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */,
|
B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */,
|
||||||
B5BD7FD61F03032400621BC2 /* TorrentHTTPTrackerTests.swift in Sources */,
|
B5BD7FD61F03032400621BC2 /* TorrentHTTPTrackerTests.swift in Sources */,
|
||||||
B556D5AE1F0BA1FD00277B8D /* GCDAsyncUdpSocketStub.swift in Sources */,
|
B556D5AE1F0BA1FD00277B8D /* GCDAsyncUdpSocketStub.swift in Sources */,
|
||||||
@@ -758,6 +769,7 @@
|
|||||||
B55317E01F02FE1500909ADF /* URLEncodeTests.swift in Sources */,
|
B55317E01F02FE1500909ADF /* URLEncodeTests.swift in Sources */,
|
||||||
B558F4851F0A73D000438BB4 /* InternetProtocolTests.swift in Sources */,
|
B558F4851F0A73D000438BB4 /* InternetProtocolTests.swift in Sources */,
|
||||||
B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */,
|
B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */,
|
||||||
|
B59E1B281F0E6EA3007753CE /* BitTorrentTestMacros.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,9 +46,7 @@ struct TorrentPeerInfo {
|
|||||||
$0.pointee
|
$0.pointee
|
||||||
}
|
}
|
||||||
let port = Int(portu16)
|
let port = Int(portu16)
|
||||||
|
|
||||||
print("Found peer: \(ip1).\(ip2).\(ip3).\(ip4):\(port)")
|
|
||||||
|
|
||||||
let peer = TorrentPeerInfo(ip: "\(ip1).\(ip2).\(ip3).\(ip4)", port: port, peerId: nil)
|
let peer = TorrentPeerInfo(ip: "\(ip1).\(ip2).\(ip3).\(ip4)", port: port, peerId: nil)
|
||||||
result.append(peer)
|
result.append(peer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,26 +8,6 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
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 {
|
class TorrentHTTPTracker {
|
||||||
|
|
||||||
let metaInfo: TorrentMetaInfo
|
let metaInfo: TorrentMetaInfo
|
||||||
@@ -42,7 +22,7 @@ class TorrentHTTPTracker {
|
|||||||
|
|
||||||
func announceClient(with peerId: String,
|
func announceClient(with peerId: String,
|
||||||
port: Int,
|
port: Int,
|
||||||
event: TorrentHTTPTrackerEvent = .started,
|
event: TorrentTrackerEvent = .started,
|
||||||
infoHash: Data,
|
infoHash: Data,
|
||||||
numberOfBytesRemaining: Int,
|
numberOfBytesRemaining: Int,
|
||||||
numberOfBytesUploaded: Int,
|
numberOfBytesUploaded: Int,
|
||||||
@@ -68,9 +48,9 @@ class TorrentHTTPTracker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let data = response.responseData {
|
if let data = response.responseData {
|
||||||
if let result = TorrentHTTPTrackerResponse(data: data) {
|
if let result = TorrentTrackerResponse(bencode: data) {
|
||||||
self!.delegate?.torrentTracker(self!, receivedResponse: result)
|
self!.delegate?.torrentTracker(self!, receivedResponse: result)
|
||||||
} else if let errorMessage = TorrentHTTPTrackerResponse.errorMessage(fromResponseData: data) {
|
} else if let errorMessage = TorrentTrackerResponse.errorMessage(fromResponseData: data) {
|
||||||
self!.delegate?.torrentTracker(self!, receivedErrorMessage: errorMessage)
|
self!.delegate?.torrentTracker(self!, receivedErrorMessage: errorMessage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,17 +12,17 @@ import XCTest
|
|||||||
class TorrentTrackerDelegateSpy: TorrentTrackerDelegate {
|
class TorrentTrackerDelegateSpy: TorrentTrackerDelegate {
|
||||||
|
|
||||||
var receivedResponseCalled = false
|
var receivedResponseCalled = false
|
||||||
var receivedResponseParameter: TorrentHTTPTrackerResponse? = nil
|
var receivedResponseParameter: TorrentTrackerResponse? = nil
|
||||||
|
|
||||||
var receivedErrorMessageCalled = false
|
var receivedErrorMessageCalled = false
|
||||||
var receivedErrorMessageParameter: String? = nil
|
var receivedErrorMessageParameter: String? = nil
|
||||||
|
|
||||||
func torrentTracker(_ sender: TorrentHTTPTracker, receivedResponse response: TorrentHTTPTrackerResponse) {
|
func torrentTracker(_ sender: Any, receivedResponse response: TorrentTrackerResponse) {
|
||||||
receivedResponseCalled = true
|
receivedResponseCalled = true
|
||||||
receivedResponseParameter = response
|
receivedResponseParameter = response
|
||||||
}
|
}
|
||||||
|
|
||||||
func torrentTracker(_ sender: TorrentHTTPTracker, receivedErrorMessage errorMessage: String) {
|
func torrentTracker(_ sender: Any, receivedErrorMessage errorMessage: String) {
|
||||||
receivedErrorMessageCalled = true
|
receivedErrorMessageCalled = true
|
||||||
receivedErrorMessageParameter = errorMessage
|
receivedErrorMessageParameter = errorMessage
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ class TorrentHTTPTrackerTests: XCTestCase {
|
|||||||
sut.delegate = delegateSpy
|
sut.delegate = delegateSpy
|
||||||
}
|
}
|
||||||
|
|
||||||
func performAnnounce(withEvent event: TorrentHTTPTrackerEvent) {
|
func performAnnounce(withEvent event: TorrentTrackerEvent) {
|
||||||
sut.announceClient(with: "peerId",
|
sut.announceClient(with: "peerId",
|
||||||
port: 123,
|
port: 123,
|
||||||
event: event,
|
event: event,
|
||||||
|
|||||||
@@ -9,7 +9,42 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import BEncode
|
import BEncode
|
||||||
|
|
||||||
struct TorrentHTTPTrackerResponse {
|
protocol TorrentTrackerDelegate: class {
|
||||||
|
func torrentTracker(_ sender: Any, receivedResponse response: TorrentTrackerResponse)
|
||||||
|
func torrentTracker(_ sender: Any, receivedErrorMessage errorMessage: String)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TorrentTrackerEvent {
|
||||||
|
case none, started, stopped, completed
|
||||||
|
|
||||||
|
var name: String {
|
||||||
|
switch self {
|
||||||
|
case .none:
|
||||||
|
return "none"
|
||||||
|
case .started:
|
||||||
|
return "started"
|
||||||
|
case .stopped:
|
||||||
|
return "stopped"
|
||||||
|
case .completed:
|
||||||
|
return "completed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var udpDataRepresentation: Data {
|
||||||
|
switch self {
|
||||||
|
case .none:
|
||||||
|
return UInt32(0).toData()
|
||||||
|
case .started:
|
||||||
|
return UInt32(2).toData()
|
||||||
|
case .stopped:
|
||||||
|
return UInt32(3).toData()
|
||||||
|
case .completed:
|
||||||
|
return UInt32(1).toData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TorrentTrackerResponse {
|
||||||
|
|
||||||
let peers: [TorrentPeerInfo]
|
let peers: [TorrentPeerInfo]
|
||||||
let numberOfPeersComplete: Int // Seeders
|
let numberOfPeersComplete: Int // Seeders
|
||||||
@@ -40,9 +75,9 @@ struct TorrentHTTPTrackerResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TorrentHTTPTrackerResponse {
|
extension TorrentTrackerResponse {
|
||||||
|
|
||||||
init?(data: Data) {
|
init?(bencode data: Data) {
|
||||||
let bencode: [String: Any]
|
let bencode: [String: Any]
|
||||||
do {
|
do {
|
||||||
bencode = try BEncoder.decodeStringKeyedDictionary(data)
|
bencode = try BEncoder.decodeStringKeyedDictionary(data)
|
||||||
@@ -79,7 +114,7 @@ extension TorrentHTTPTrackerResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension TorrentHTTPTrackerResponse {
|
extension TorrentTrackerResponse {
|
||||||
|
|
||||||
static func errorMessage(fromResponseData data: Data) -> String? {
|
static func errorMessage(fromResponseData data: Data) -> String? {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
//
|
||||||
|
// TorrentUDPTracker.swift
|
||||||
|
// BitTorrent
|
||||||
|
//
|
||||||
|
// Created by Ben Davis on 04/07/2017.
|
||||||
|
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import BEncode
|
||||||
|
|
||||||
|
private let PROTOCOL_ID = UInt64(0x41727101980).toData() // magic constant (protocol_id)
|
||||||
|
|
||||||
|
private let CONNECTION_ACTION = UInt32(0).toData()
|
||||||
|
private let ANNOUNCE_ACTION = UInt32(1).toData()
|
||||||
|
private let ERROR_ACTION = UInt32(3).toData()
|
||||||
|
|
||||||
|
class TorrentUDPTracker {
|
||||||
|
|
||||||
|
var enableLogging = false
|
||||||
|
|
||||||
|
weak var delegate: TorrentTrackerDelegate?
|
||||||
|
|
||||||
|
private let announceURL: URL
|
||||||
|
private var discoveredHostIpAddress: String?
|
||||||
|
private let udpConnection: UDPConnectionProtocol
|
||||||
|
|
||||||
|
private var pendingAnnounce: ((_ transactionId: Data, _ connectionId: Data)->Void)?
|
||||||
|
private var pendingTransactionId: Data?
|
||||||
|
|
||||||
|
init(announceURL: URL, port: UInt16, udpConnection: UDPConnectionProtocol = UDPConnection()) {
|
||||||
|
self.announceURL = announceURL
|
||||||
|
self.udpConnection = udpConnection
|
||||||
|
udpConnection.delegate = self
|
||||||
|
udpConnection.startListening(on: port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func announceClient(with peerId: String,
|
||||||
|
port: Int,
|
||||||
|
event: TorrentTrackerEvent = .started,
|
||||||
|
infoHash: Data,
|
||||||
|
numberOfBytesRemaining: Int,
|
||||||
|
numberOfBytesUploaded: Int,
|
||||||
|
numberOfBytesDownloaded: Int,
|
||||||
|
numberOfPeersToFetch: Int) {
|
||||||
|
|
||||||
|
guard let host = getHostIpAddress() else { return }
|
||||||
|
let announcePort = UInt16(announceURL.port ?? 80)
|
||||||
|
|
||||||
|
log("Will connect to UDP tracker: \(host):\(announcePort)")
|
||||||
|
|
||||||
|
let transactionId = makeTransactionId()
|
||||||
|
let payload = makeConnectionPayload(with: transactionId)
|
||||||
|
udpConnection.send(payload, toHost: host, port: announcePort, timeout: 10)
|
||||||
|
|
||||||
|
pendingAnnounce = { [weak self] (responseTransactionId, connectionId) in
|
||||||
|
|
||||||
|
guard let strongSelf = self else { return }
|
||||||
|
guard responseTransactionId == transactionId else { return }
|
||||||
|
|
||||||
|
self?.log("Will announce to UDP tracker: \(host):\(announcePort)")
|
||||||
|
|
||||||
|
var payload = connectionId // 0 64-bit integer connection_id
|
||||||
|
payload += ANNOUNCE_ACTION // 8 32-bit integer action 1 // announce
|
||||||
|
payload += strongSelf.makeTransactionId() // 12 32-bit integer transaction_id
|
||||||
|
payload += infoHash // 16 20-byte string info_hash
|
||||||
|
payload += peerId.data(using: .ascii)! // 36 20-byte string peer_id
|
||||||
|
payload += UInt64(numberOfBytesDownloaded).toData() // 56 64-bit integer downloaded
|
||||||
|
payload += UInt64(numberOfBytesRemaining).toData() // 64 64-bit integer left
|
||||||
|
payload += UInt64(numberOfBytesUploaded).toData() // 72 64-bit integer uploaded
|
||||||
|
payload += event.udpDataRepresentation // 80 32-bit integer event
|
||||||
|
payload += UInt32(0).toData() // 84 32-bit integer IP address 0 // default
|
||||||
|
payload += UInt32(0).toData() // 88 32-bit integer key 0 // default
|
||||||
|
payload += UInt32(numberOfPeersToFetch).toData() // 92 32-bit integer num_want -1 // default
|
||||||
|
payload += UInt16(port).toData() // 96 16-bit integer port
|
||||||
|
|
||||||
|
strongSelf.udpConnection.send(payload, toHost: host, port: announcePort, timeout: 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func getHostIpAddress() -> String? {
|
||||||
|
guard discoveredHostIpAddress == nil else {
|
||||||
|
return discoveredHostIpAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = InternetProtocol.getIPAddress(of: announceURL.host!)
|
||||||
|
discoveredHostIpAddress = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeConnectionPayload(with transactionId: Data) -> Data {
|
||||||
|
return PROTOCOL_ID + CONNECTION_ACTION + transactionId
|
||||||
|
}
|
||||||
|
|
||||||
|
private func makeTransactionId() -> Data {
|
||||||
|
let result = arc4random().toData()
|
||||||
|
pendingTransactionId = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TorrentUDPTracker: UDPConnectionDelegate {
|
||||||
|
|
||||||
|
func udpConnection(_ sender: UDPConnectionProtocol, receivedData data: Data, fromHost host: String) {
|
||||||
|
|
||||||
|
let action = Data(data[0..<4])
|
||||||
|
|
||||||
|
log("Got response from UDP tracker \(host)")
|
||||||
|
|
||||||
|
if action == CONNECTION_ACTION {
|
||||||
|
log("UDP tracker \(host) accepted connection")
|
||||||
|
parseConnectionResponse(data)
|
||||||
|
} else if action == ANNOUNCE_ACTION {
|
||||||
|
log("UDP tracker \(host) responded to announce")
|
||||||
|
parseAnnounceResponse(data)
|
||||||
|
} else if action == ERROR_ACTION {
|
||||||
|
log("UDP tracker \(host) gave error")
|
||||||
|
parseErrorResponse(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseConnectionResponse(_ response: Data) {
|
||||||
|
|
||||||
|
let transactionId = Data(response[4..<8])
|
||||||
|
let connectionId = Data(response[8..<16])
|
||||||
|
|
||||||
|
pendingAnnounce?(transactionId, connectionId)
|
||||||
|
pendingAnnounce = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func parseAnnounceResponse(_ response: Data) {
|
||||||
|
|
||||||
|
let transactionId = Data(response[4..<8])
|
||||||
|
guard pendingTransactionId == transactionId else { return }
|
||||||
|
|
||||||
|
let interval = response[8..<12].toUInt32()
|
||||||
|
let leechers = response[12..<16].toUInt32()
|
||||||
|
let seeders = response[16..<20].toUInt32()
|
||||||
|
let peers = TorrentPeerInfo.peersInfoFromBinaryModel(response[20..<response.count])
|
||||||
|
|
||||||
|
let response = TorrentTrackerResponse(peers: peers,
|
||||||
|
numberOfPeersComplete: Int(seeders),
|
||||||
|
numberOfPeersIncomplete: Int(leechers),
|
||||||
|
interval: Int(interval))
|
||||||
|
|
||||||
|
self.delegate?.torrentTracker(self, receivedResponse: response)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func parseErrorResponse(_ response: Data) {
|
||||||
|
|
||||||
|
if let errorMessage = String(data: response[8..<response.count], encoding: .utf8) {
|
||||||
|
delegate?.torrentTracker(self, receivedErrorMessage: errorMessage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension TorrentUDPTracker {
|
||||||
|
func log(_ items: Any...) {
|
||||||
|
if enableLogging { print(items) }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,370 @@
|
|||||||
|
//
|
||||||
|
// TorrentUDPTrackerTests.swift
|
||||||
|
// BitTorrent
|
||||||
|
//
|
||||||
|
// Created by Ben Davis on 04/07/2017.
|
||||||
|
// Copyright © 2017 Ben Davis. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
@testable import BitTorrent
|
||||||
|
import BEncode
|
||||||
|
|
||||||
|
class UDPConnectionStub: UDPConnectionProtocol {
|
||||||
|
|
||||||
|
weak var delegate: UDPConnectionDelegate?
|
||||||
|
|
||||||
|
var startListeningCalled = false
|
||||||
|
var startListeningParameter: UInt16?
|
||||||
|
func startListening(on port: UInt16) {
|
||||||
|
startListeningCalled = true
|
||||||
|
startListeningParameter = port
|
||||||
|
}
|
||||||
|
|
||||||
|
var sendCallCount = 0
|
||||||
|
var sendDataParameters: (data: Data, host: String, port: UInt16, timeout: TimeInterval)?
|
||||||
|
func send(_ data: Data, toHost host: String, port: UInt16, timeout: TimeInterval) {
|
||||||
|
sendCallCount += 1
|
||||||
|
sendDataParameters = (data, host, port, timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TorrentUDPTrackerTests: XCTestCase {
|
||||||
|
|
||||||
|
var sut: TorrentUDPTracker!
|
||||||
|
var torrentTrackerDelegateSpy: TorrentTrackerDelegateSpy!
|
||||||
|
var udpConnection: UDPConnectionStub!
|
||||||
|
let port: UInt16 = 123
|
||||||
|
|
||||||
|
override func setUp() {
|
||||||
|
super.setUp()
|
||||||
|
|
||||||
|
let url = URL(string: "udp://localhost:123/announce")!
|
||||||
|
udpConnection = UDPConnectionStub()
|
||||||
|
torrentTrackerDelegateSpy = TorrentTrackerDelegateSpy()
|
||||||
|
sut = TorrentUDPTracker(announceURL: url, port: 123, udpConnection: udpConnection)
|
||||||
|
sut.delegate = torrentTrackerDelegateSpy
|
||||||
|
}
|
||||||
|
|
||||||
|
func performAnnounce(withEvent event: TorrentTrackerEvent) {
|
||||||
|
sut.announceClient(with: "peerId12345678901234",
|
||||||
|
port: 789,
|
||||||
|
event: event,
|
||||||
|
infoHash: Data(bytes: [ 1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9,0 ]),
|
||||||
|
numberOfBytesRemaining: 456,
|
||||||
|
numberOfBytesUploaded: 1234,
|
||||||
|
numberOfBytesDownloaded: 4321,
|
||||||
|
numberOfPeersToFetch: 321)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_startsListeningOnPort() {
|
||||||
|
XCTAssert(udpConnection.startListeningCalled)
|
||||||
|
XCTAssertEqual(udpConnection.startListeningParameter, port)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_hostIsResolvedFromURL() {
|
||||||
|
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
|
||||||
|
XCTAssertEqual(udpConnection.sendCallCount, 1)
|
||||||
|
|
||||||
|
if let parameters = udpConnection.sendDataParameters {
|
||||||
|
XCTAssertEqual(parameters.host, "127.0.0.1")
|
||||||
|
XCTAssertEqual(parameters.port, 123)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_connectMessageSentToHost() {
|
||||||
|
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
|
||||||
|
let expectedProtocolId = UInt64(0x41727101980).toData()
|
||||||
|
let expectedAction = UInt32(0).toData()
|
||||||
|
|
||||||
|
XCTAssertEqual(udpConnection.sendCallCount, 1)
|
||||||
|
|
||||||
|
if let parameters = udpConnection.sendDataParameters {
|
||||||
|
|
||||||
|
XCTAssertEqual(parameters.data.count, 16)
|
||||||
|
|
||||||
|
let protocolId = Data(parameters.data[0..<8])
|
||||||
|
XCTAssertEqual(protocolId, expectedProtocolId)
|
||||||
|
|
||||||
|
let action = Data(parameters.data[8..<12])
|
||||||
|
XCTAssertEqual(action, expectedAction)
|
||||||
|
|
||||||
|
XCTAssertEqual(parameters.host, "127.0.0.1")
|
||||||
|
XCTAssertEqual(parameters.port, 123)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_announceSentOnConnectionAccepted() {
|
||||||
|
|
||||||
|
// Given
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
|
||||||
|
// When
|
||||||
|
let expectedAction = UInt32(1).toData()
|
||||||
|
let expectedConnectionId = simulateAcceptConnection()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(udpConnection.sendCallCount, 2)
|
||||||
|
if let parameters = udpConnection.sendDataParameters {
|
||||||
|
|
||||||
|
let connectionId = Data(parameters.data[0..<8])
|
||||||
|
XCTAssertEqual(connectionId, expectedConnectionId)
|
||||||
|
|
||||||
|
let action = Data(parameters.data[8..<12])
|
||||||
|
XCTAssertEqual(action, expectedAction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_announcePayload() {
|
||||||
|
|
||||||
|
let peerId = "peerId12345678901234"
|
||||||
|
let exampleEvent = TorrentTrackerEvent.started
|
||||||
|
let examplePort = 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
|
||||||
|
let numberOfBytesDownloaded = 4321
|
||||||
|
let numberOfPeersToFetch = 321
|
||||||
|
|
||||||
|
// Given
|
||||||
|
sut.announceClient(with: peerId,
|
||||||
|
port: examplePort,
|
||||||
|
event: exampleEvent,
|
||||||
|
infoHash: expectedInfoHash,
|
||||||
|
numberOfBytesRemaining: numberOfBytesRemaining,
|
||||||
|
numberOfBytesUploaded: numberOfBytesUploaded,
|
||||||
|
numberOfBytesDownloaded: numberOfBytesDownloaded,
|
||||||
|
numberOfPeersToFetch: numberOfPeersToFetch)
|
||||||
|
// When
|
||||||
|
let expectedConnectionId = simulateAcceptConnection()
|
||||||
|
let expectedAction = UInt32(1).toData()
|
||||||
|
let expectedPeerId = peerId.data(using: .ascii)!
|
||||||
|
let expectedDownloaded = UInt64(numberOfBytesDownloaded).toData()
|
||||||
|
let expectedLeft = UInt64(numberOfBytesRemaining).toData()
|
||||||
|
let expectedUploaded = UInt64(numberOfBytesUploaded).toData()
|
||||||
|
let expectedEvent = exampleEvent.udpDataRepresentation
|
||||||
|
let expectedIPAddress = UInt32(0).toData() // default value
|
||||||
|
let expectedNumWant = UInt32(numberOfPeersToFetch).toData()
|
||||||
|
let expectedPort = UInt16(examplePort).toData()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(udpConnection.sendCallCount, 2)
|
||||||
|
if let data = udpConnection.sendDataParameters?.data {
|
||||||
|
|
||||||
|
XCTAssertEqual(data.count, 98)
|
||||||
|
|
||||||
|
let connectionId = Data(data[0..<8])
|
||||||
|
let action = Data(data[8..<12])
|
||||||
|
// let transactionId = Data(data[12..<16])
|
||||||
|
let infoHash = Data(data[16..<36])
|
||||||
|
let peerId = Data(data[36..<56])
|
||||||
|
let downloaded = Data(data[56..<64])
|
||||||
|
let left = Data(data[64..<72])
|
||||||
|
let uploaded = Data(data[72..<80])
|
||||||
|
let event = Data(data[80..<84])
|
||||||
|
let ipAddress = Data(data[84..<88])
|
||||||
|
// let key = Data(data[88..<92])
|
||||||
|
let numWant = Data(data[92..<96])
|
||||||
|
let port = Data(data[96..<98])
|
||||||
|
|
||||||
|
XCTAssertEqual(connectionId, expectedConnectionId)
|
||||||
|
XCTAssertEqual(action, expectedAction)
|
||||||
|
XCTAssertEqual(infoHash, expectedInfoHash)
|
||||||
|
XCTAssertEqual(peerId, expectedPeerId)
|
||||||
|
XCTAssertEqual(downloaded, expectedDownloaded)
|
||||||
|
XCTAssertEqual(left, expectedLeft)
|
||||||
|
XCTAssertEqual(uploaded, expectedUploaded)
|
||||||
|
XCTAssertEqual(event, expectedEvent)
|
||||||
|
XCTAssertEqual(ipAddress, expectedIPAddress)
|
||||||
|
XCTAssertEqual(numWant, expectedNumWant)
|
||||||
|
XCTAssertEqual(port, expectedPort)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_basicResponseParsing() {
|
||||||
|
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
_ = simulateAcceptConnection()
|
||||||
|
|
||||||
|
let interval = 1
|
||||||
|
let seeders = 2
|
||||||
|
let leechers = 3
|
||||||
|
|
||||||
|
simulateAnnounceResponse(interval: interval,
|
||||||
|
leechers: leechers,
|
||||||
|
seeders: seeders,
|
||||||
|
peers: Data())
|
||||||
|
|
||||||
|
XCTAssert(torrentTrackerDelegateSpy.receivedResponseCalled)
|
||||||
|
guard let response = torrentTrackerDelegateSpy.receivedResponseParameter else { return }
|
||||||
|
|
||||||
|
XCTAssertEqual(response.interval, 1)
|
||||||
|
XCTAssertEqual(response.numberOfPeersComplete, seeders)
|
||||||
|
XCTAssertEqual(response.numberOfPeersIncomplete, leechers)
|
||||||
|
XCTAssertEqual(response.peers, [])
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_parsingPeers() {
|
||||||
|
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
_ = simulateAcceptConnection()
|
||||||
|
|
||||||
|
let peers = examplePeersResponse(with: [
|
||||||
|
(127,0,0,1, 15383),
|
||||||
|
(216,58,198,14, 4321),
|
||||||
|
])
|
||||||
|
|
||||||
|
simulateAnnounceResponse(interval: 1,
|
||||||
|
leechers: 2,
|
||||||
|
seeders: 3,
|
||||||
|
peers: peers)
|
||||||
|
|
||||||
|
XCTAssert(torrentTrackerDelegateSpy.receivedResponseCalled)
|
||||||
|
guard let response = torrentTrackerDelegateSpy.receivedResponseParameter else { return }
|
||||||
|
|
||||||
|
XCTAssertEqual(response.peers.count, 2)
|
||||||
|
|
||||||
|
XCTAssertEqual(response.peers.first!.ip, "127.0.0.1")
|
||||||
|
XCTAssertEqual(response.peers.first!.port, 15383)
|
||||||
|
XCTAssertNil(response.peers.first!.peerId)
|
||||||
|
|
||||||
|
XCTAssertEqual(response.peers.last!.ip, "216.58.198.14")
|
||||||
|
XCTAssertEqual(response.peers.last!.port, 4321)
|
||||||
|
XCTAssertNil(response.peers.last!.peerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func examplePeersResponse(with peers: [(UInt8, UInt8, UInt8, UInt8, UInt16)]) -> Data {
|
||||||
|
|
||||||
|
var result = Data()
|
||||||
|
for peer in peers {
|
||||||
|
|
||||||
|
let ip = Data(bytes: [peer.0, peer.1, peer.2, peer.3])
|
||||||
|
result.append(ip)
|
||||||
|
|
||||||
|
let port = peer.4.toData()
|
||||||
|
result.append(port)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_announceMessageForOldTransactionIdIsIgnored() {
|
||||||
|
|
||||||
|
// Given
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
_ = simulateAcceptConnection()
|
||||||
|
guard let connectionParameters = udpConnection.sendDataParameters else { return }
|
||||||
|
let oldTransactionId = connectionParameters.data[4..<8]
|
||||||
|
|
||||||
|
// When
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
_ = simulateAcceptConnection()
|
||||||
|
simulateAnnounceResponse(interval: 1, leechers: 2, seeders: 3, peers: Data(), transactionId: oldTransactionId)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertFalse(torrentTrackerDelegateSpy.receivedResponseCalled)
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_connectionMessageForOldTransactionIdIsIgnored() {
|
||||||
|
|
||||||
|
// Given
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
guard let connectionParameters = udpConnection.sendDataParameters else { return }
|
||||||
|
let oldTransactionId = connectionParameters.data[4..<8]
|
||||||
|
|
||||||
|
// When
|
||||||
|
performAnnounce(withEvent: .started)
|
||||||
|
|
||||||
|
let connectionId = arc4random().toData() + arc4random().toData()
|
||||||
|
let actionData = UInt32(0).toData() // Action 0 = connection
|
||||||
|
let connectionResponse = actionData + oldTransactionId + connectionId
|
||||||
|
|
||||||
|
udpConnection.delegate?.udpConnection(udpConnection,
|
||||||
|
receivedData: connectionResponse,
|
||||||
|
fromHost: "127.0.0.1")
|
||||||
|
|
||||||
|
// Then
|
||||||
|
XCTAssertEqual(udpConnection.sendCallCount, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: -
|
||||||
|
|
||||||
|
func simulateAnnounceResponse(interval: Int,
|
||||||
|
leechers: Int,
|
||||||
|
seeders: Int,
|
||||||
|
peers: Data) {
|
||||||
|
|
||||||
|
guard let announceParameters = udpConnection.sendDataParameters else { return }
|
||||||
|
let transactionId = announceParameters.data[12..<16]
|
||||||
|
|
||||||
|
simulateAnnounceResponse(interval: interval,
|
||||||
|
leechers: leechers,
|
||||||
|
seeders: seeders,
|
||||||
|
peers: peers,
|
||||||
|
transactionId: transactionId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func simulateAnnounceResponse(interval: Int,
|
||||||
|
leechers: Int,
|
||||||
|
seeders: Int,
|
||||||
|
peers: Data,
|
||||||
|
transactionId: Data) {
|
||||||
|
|
||||||
|
var announceResponse = UInt32(1).toData()
|
||||||
|
announceResponse += transactionId
|
||||||
|
announceResponse += UInt32(interval).toData()
|
||||||
|
announceResponse += UInt32(leechers).toData()
|
||||||
|
announceResponse += UInt32(seeders).toData()
|
||||||
|
announceResponse += peers
|
||||||
|
|
||||||
|
udpConnection.delegate?.udpConnection(udpConnection,
|
||||||
|
receivedData: announceResponse,
|
||||||
|
fromHost: "127.0.0.1")
|
||||||
|
}
|
||||||
|
|
||||||
|
func simulateAcceptConnection() -> Data {
|
||||||
|
|
||||||
|
let connectionId = arc4random().toData() + arc4random().toData()
|
||||||
|
|
||||||
|
guard let connectionParameters = udpConnection.sendDataParameters else {
|
||||||
|
return connectionId
|
||||||
|
}
|
||||||
|
|
||||||
|
let transactionId = connectionParameters.data[12..<16]
|
||||||
|
let actionData = UInt32(0).toData() // Action 0 = connection
|
||||||
|
let connectionResponse = actionData + transactionId + connectionId
|
||||||
|
|
||||||
|
udpConnection.delegate?.udpConnection(udpConnection,
|
||||||
|
receivedData: connectionResponse,
|
||||||
|
fromHost: "127.0.0.1")
|
||||||
|
|
||||||
|
return connectionId
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Error handling
|
||||||
|
|
||||||
|
func delegateCalledOnError() {
|
||||||
|
simulateErrorResponse(withError: "Error Message")
|
||||||
|
|
||||||
|
XCTAssert(torrentTrackerDelegateSpy.receivedErrorMessageCalled)
|
||||||
|
XCTAssertEqual(torrentTrackerDelegateSpy.receivedErrorMessageParameter!, "Error Message")
|
||||||
|
}
|
||||||
|
|
||||||
|
func simulateErrorResponse(withError errorString: String) {
|
||||||
|
|
||||||
|
guard let connectionParameters = udpConnection.sendDataParameters else { return }
|
||||||
|
let transactionId = connectionParameters.data[4..<8]
|
||||||
|
|
||||||
|
let connectionResponse = UInt32(3).toData() + // Action 3 = error
|
||||||
|
transactionId + // Responding to transaction
|
||||||
|
errorString.data(using: .utf8)! // Error message
|
||||||
|
|
||||||
|
udpConnection.delegate?.udpConnection(udpConnection,
|
||||||
|
receivedData: connectionResponse,
|
||||||
|
fromHost: "127.0.0.1")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,112 +8,94 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
let IOS_CELLULAR_INTERFACE_NAME = "pdp_ip0"
|
struct InternetProtocol {
|
||||||
let IOS_WIFI_INTERFACE_NAME = "en0"
|
|
||||||
let IP_ADDR_IPv4_INTERFACE_NAME = "ipv4"
|
|
||||||
let IP_ADDR_IPv6_INTERFACE_NAME = "ipv6"
|
|
||||||
|
|
||||||
func ipAddress(fromSockAddrData data: Data) -> String? {
|
|
||||||
let socketAddress = data.withUnsafeBytes() { (pointer: UnsafePointer<sockaddr_in>) in
|
|
||||||
return pointer.pointee
|
|
||||||
}
|
|
||||||
guard let resultCString = inet_ntoa(socketAddress.sin_addr) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return String(cString: resultCString)
|
|
||||||
}
|
|
||||||
|
|
||||||
func port(fromSockAddrData data: Data) -> UInt16 {
|
|
||||||
let socketAddress = data.withUnsafeBytes() { (pointer: UnsafePointer<sockaddr_in>) in
|
|
||||||
return pointer.pointee
|
|
||||||
}
|
|
||||||
return socketAddress.sin_port
|
|
||||||
}
|
|
||||||
|
|
||||||
// + (NSString*)ipAddressFromSockAddrData:(NSData*)data {
|
|
||||||
// struct sockaddr_in * socketAddress = (struct sockaddr_in *)data.bytes;
|
|
||||||
// struct in_addr ipAsStruct = ((struct sockaddr_in)*(socketAddress)).sin_addr;
|
|
||||||
// char *buff;
|
|
||||||
// buff = inet_ntoa(ipAsStruct);
|
|
||||||
// NSString *ip = [NSString stringWithUTF8String:buff];
|
|
||||||
// return ip;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// + (uint16_t)portFromSockAddrData:(NSData*)data {
|
|
||||||
// struct sockaddr_in * socketAddressPtr = (struct sockaddr_in *)data.bytes;
|
|
||||||
// struct sockaddr_in socketAddress = ((struct sockaddr_in)*(socketAddressPtr));
|
|
||||||
// return socketAddress.sin_port;
|
|
||||||
//}
|
|
||||||
|
|
||||||
func getIPAddress(of hostname: String) -> String? {
|
|
||||||
|
|
||||||
guard let hostnameCString = hostname.cString(using: .ascii),
|
static let IOS_CELLULAR_INTERFACE_NAME = "pdp_ip0"
|
||||||
let hostEntry = gethostbyname(hostnameCString)?.pointee,
|
static let IOS_WIFI_INTERFACE_NAME = "en0"
|
||||||
let hostAddressList = hostEntry.h_addr_list?.pointee else {
|
static let IP_ADDR_IPv4_INTERFACE_NAME = "ipv4"
|
||||||
return nil
|
static let IP_ADDR_IPv6_INTERFACE_NAME = "ipv6"
|
||||||
}
|
|
||||||
|
|
||||||
let firstHostAddress = hostAddressList.withMemoryRebound(to: in_addr.self, capacity: 1) { $0.pointee }
|
static func ipAddress(fromSockAddrData data: Data) -> String? {
|
||||||
let firstHostAddressCString = inet_ntoa(firstHostAddress)!
|
let socketAddress = data.withUnsafeBytes() { (pointer: UnsafePointer<sockaddr_in>) in
|
||||||
return String(cString: firstHostAddressCString)
|
return pointer.pointee
|
||||||
}
|
|
||||||
|
|
||||||
func getLocalIPAddress(preferIPv4: Bool = true) -> String? {
|
|
||||||
|
|
||||||
// Prefer wifi over cellular
|
|
||||||
let searchArray = preferIPv4 ?
|
|
||||||
[
|
|
||||||
IOS_WIFI_INTERFACE_NAME + "/" + IP_ADDR_IPv4_INTERFACE_NAME,
|
|
||||||
IOS_WIFI_INTERFACE_NAME + "/" + IP_ADDR_IPv6_INTERFACE_NAME,
|
|
||||||
IOS_CELLULAR_INTERFACE_NAME + "/" + IP_ADDR_IPv4_INTERFACE_NAME,
|
|
||||||
IOS_CELLULAR_INTERFACE_NAME + "/" + IP_ADDR_IPv6_INTERFACE_NAME,
|
|
||||||
] :
|
|
||||||
[
|
|
||||||
IOS_WIFI_INTERFACE_NAME + "/" + IP_ADDR_IPv6_INTERFACE_NAME,
|
|
||||||
IOS_WIFI_INTERFACE_NAME + "/" + IP_ADDR_IPv4_INTERFACE_NAME,
|
|
||||||
IOS_CELLULAR_INTERFACE_NAME + "/" + IP_ADDR_IPv6_INTERFACE_NAME,
|
|
||||||
IOS_CELLULAR_INTERFACE_NAME + "/" + IP_ADDR_IPv4_INTERFACE_NAME,
|
|
||||||
]
|
|
||||||
|
|
||||||
let addresses = getLocalIPAddresses()
|
|
||||||
|
|
||||||
for searchItem in searchArray {
|
|
||||||
if let result = addresses[searchItem] {
|
|
||||||
return result
|
|
||||||
}
|
}
|
||||||
|
guard let resultCString = inet_ntoa(socketAddress.sin_addr) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return String(cString: resultCString)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
static func port(fromSockAddrData data: Data) -> UInt16 {
|
||||||
}
|
let socketAddress = data.withUnsafeBytes() { (pointer: UnsafePointer<sockaddr_in>) in
|
||||||
|
return pointer.pointee
|
||||||
func getLocalIPAddresses() -> [String: String] {
|
}
|
||||||
|
return socketAddress.sin_port
|
||||||
var addresses: [String: String] = [:]
|
|
||||||
|
|
||||||
// Get list of all network interfaces on the local machine:
|
|
||||||
var ifaddrsPointer : UnsafeMutablePointer<ifaddrs>?
|
|
||||||
guard getifaddrs(&ifaddrsPointer) == 0, let ifaddrsList = ifaddrsPointer?.pointee else {
|
|
||||||
return [:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each network interface ...
|
static func getIPAddress(of hostname: String) -> String? {
|
||||||
for ifaddrs in ifaddrsList {
|
|
||||||
if !ifaddrs.isUpAndRunning || ifaddrs.isLoopbackNet {
|
guard let hostnameCString = hostname.cString(using: .ascii),
|
||||||
continue
|
let hostEntry = gethostbyname(hostnameCString)?.pointee,
|
||||||
|
let hostAddressList = hostEntry.h_addr_list?.pointee else {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if ifaddrs.isIpv4 || ifaddrs.isIpv6 {
|
let firstHostAddress = hostAddressList.withMemoryRebound(to: in_addr.self, capacity: 1) { $0.pointee }
|
||||||
if let addressString = ifaddrs.convertToIPString(), let name = ifaddrs.nameAndTypeString() {
|
let firstHostAddressCString = inet_ntoa(firstHostAddress)!
|
||||||
addresses[name] = addressString
|
return String(cString: firstHostAddressCString)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
freeifaddrs(ifaddrsPointer)
|
static func getLocalIPAddress(preferIPv4: Bool = true) -> String? {
|
||||||
|
|
||||||
|
// Prefer wifi over cellular
|
||||||
|
let searchArray = [
|
||||||
|
ifaddrs.nameAndTypeString(from: IOS_WIFI_INTERFACE_NAME, isIpv4: preferIPv4),
|
||||||
|
ifaddrs.nameAndTypeString(from: IOS_WIFI_INTERFACE_NAME, isIpv4: !preferIPv4),
|
||||||
|
ifaddrs.nameAndTypeString(from: IOS_CELLULAR_INTERFACE_NAME, isIpv4: preferIPv4),
|
||||||
|
ifaddrs.nameAndTypeString(from: IOS_CELLULAR_INTERFACE_NAME, isIpv4: !preferIPv4),
|
||||||
|
]
|
||||||
|
|
||||||
|
let addresses = getLocalIPAddresses()
|
||||||
|
|
||||||
|
for searchItem in searchArray {
|
||||||
|
if let result = addresses[searchItem] {
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return addresses
|
static func getLocalIPAddresses() -> [String: String] {
|
||||||
|
|
||||||
|
var addresses: [String: String] = [:]
|
||||||
|
|
||||||
|
// Get list of all network interfaces on the local machine:
|
||||||
|
var ifaddrsPointer : UnsafeMutablePointer<ifaddrs>?
|
||||||
|
guard getifaddrs(&ifaddrsPointer) == 0, let ifaddrsList = ifaddrsPointer?.pointee else {
|
||||||
|
return [:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// For each network interface ...
|
||||||
|
for ifaddrs in ifaddrsList {
|
||||||
|
if !ifaddrs.isUpAndRunning || ifaddrs.isLoopbackNet {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ifaddrs.isIpv4 || ifaddrs.isIpv6 {
|
||||||
|
if let addressString = ifaddrs.convertToIPString(), let name = ifaddrs.nameAndTypeString() {
|
||||||
|
addresses[name] = addressString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
freeifaddrs(ifaddrsPointer)
|
||||||
|
|
||||||
|
return addresses
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public class ifaddrsIterator: IteratorProtocol {
|
public class ifaddrsIterator: IteratorProtocol {
|
||||||
public typealias Element = ifaddrs
|
public typealias Element = ifaddrs
|
||||||
|
|
||||||
@@ -173,7 +155,13 @@ extension ifaddrs {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "\(name) - " + (isIpv4 ? IP_ADDR_IPv4_INTERFACE_NAME : IP_ADDR_IPv6_INTERFACE_NAME)
|
return ifaddrs.nameAndTypeString(from: name, isIpv4: isIpv4)
|
||||||
|
}
|
||||||
|
|
||||||
|
static func nameAndTypeString(from name: String, isIpv4: Bool) -> String {
|
||||||
|
return name + "/" + (isIpv4 ?
|
||||||
|
InternetProtocol.IP_ADDR_IPv4_INTERFACE_NAME :
|
||||||
|
InternetProtocol.IP_ADDR_IPv6_INTERFACE_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,36 +12,36 @@ import XCTest
|
|||||||
class InternetProtocolTests: XCTestCase {
|
class InternetProtocolTests: XCTestCase {
|
||||||
|
|
||||||
func test_canDecodeLocalhost() {
|
func test_canDecodeLocalhost() {
|
||||||
let result = getIPAddress(of: "localhost")
|
let result = InternetProtocol.getIPAddress(of: "localhost")
|
||||||
XCTAssertNotNil(result)
|
XCTAssertNotNil(result)
|
||||||
XCTAssertEqual(result!, "127.0.0.1")
|
XCTAssertEqual(result!, "127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_invalidHostnameReturnsNil() {
|
func test_invalidHostnameReturnsNil() {
|
||||||
let result = getIPAddress(of: "asldfjhablskhdbj")
|
let result = InternetProtocol.getIPAddress(of: "asldfjhablskhdbj")
|
||||||
XCTAssertNil(result)
|
XCTAssertNil(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_nonAsciiHostnameReturnsNil() {
|
func test_nonAsciiHostnameReturnsNil() {
|
||||||
let result = getIPAddress(of: "🙁")
|
let result = InternetProtocol.getIPAddress(of: "🙁")
|
||||||
XCTAssertNil(result)
|
XCTAssertNil(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_canDecodeGoogle() {
|
func test_canDecodeGoogle() {
|
||||||
let result = getIPAddress(of: "google.com")
|
let result = InternetProtocol.getIPAddress(of: "google.com")
|
||||||
XCTAssertNotNil(result)
|
XCTAssertNotNil(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_canDecodeIPv4AddressFromData() {
|
func test_canDecodeIPv4AddressFromData() {
|
||||||
let data = Data(bytes: [16,2,122,105,127,0,0,1,0,0,0,0,0,0,0,0])
|
let data = Data(bytes: [16,2,122,105,127,0,0,1,0,0,0,0,0,0,0,0])
|
||||||
let result = ipAddress(fromSockAddrData: data)
|
let result = InternetProtocol.ipAddress(fromSockAddrData: data)
|
||||||
XCTAssertNotNil(result)
|
XCTAssertNotNil(result)
|
||||||
XCTAssertEqual(result, "127.0.0.1")
|
XCTAssertEqual(result, "127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|
||||||
func test_canDecodeSocketPortFromData() {
|
func test_canDecodeSocketPortFromData() {
|
||||||
let data = Data(bytes: [16,2,122,105,127,0,0,1,0,0,0,0,0,0,0,0])
|
let data = Data(bytes: [16,2,122,105,127,0,0,1,0,0,0,0,0,0,0,0])
|
||||||
let result = port(fromSockAddrData: data)
|
let result = InternetProtocol.port(fromSockAddrData: data)
|
||||||
XCTAssertNotNil(result)
|
XCTAssertNotNil(result)
|
||||||
XCTAssertEqual(result, 27002)
|
XCTAssertEqual(result, 27002)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,32 +9,32 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
import CocoaAsyncSocket
|
import CocoaAsyncSocket
|
||||||
|
|
||||||
|
protocol UDPConnectionProtocol: class {
|
||||||
|
weak var delegate: UDPConnectionDelegate? { set get }
|
||||||
|
func startListening(on port: UInt16)
|
||||||
|
func send(_ data: Data, toHost host: String, port: UInt16, timeout: TimeInterval)
|
||||||
|
}
|
||||||
|
|
||||||
protocol UDPConnectionDelegate: class {
|
protocol UDPConnectionDelegate: class {
|
||||||
func udpConnection(_ sender: UDPConnection, receivedData data: Data, fromHost host: String)
|
func udpConnection(_ sender: UDPConnectionProtocol, receivedData data: Data, fromHost host: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This class is a thin wrapper around the socket library to protect against changes
|
/// This class is a thin wrapper around the socket library to protect against changes
|
||||||
/// in its interface, and to allow me to replace CocoaAsyncSocket with a swift framework
|
/// in its interface, and to allow me to replace CocoaAsyncSocket with a swift framework
|
||||||
/// one day.
|
/// one day.
|
||||||
class UDPConnection: NSObject {
|
class UDPConnection: NSObject, UDPConnectionProtocol {
|
||||||
|
|
||||||
weak var delegate: UDPConnectionDelegate?
|
weak var delegate: UDPConnectionDelegate?
|
||||||
|
|
||||||
private let socket: GCDAsyncUdpSocket
|
private let socket: GCDAsyncUdpSocket
|
||||||
|
|
||||||
// Designated init for testing
|
init(socket: GCDAsyncUdpSocket = GCDAsyncUdpSocket()) {
|
||||||
init(socket: GCDAsyncUdpSocket) {
|
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
super.init()
|
super.init()
|
||||||
socket.setDelegate(self)
|
socket.setDelegate(self)
|
||||||
socket.synchronouslySetDelegateQueue(.main)
|
socket.synchronouslySetDelegateQueue(.main)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Useful init which should be used
|
|
||||||
override convenience init() {
|
|
||||||
self.init(socket: GCDAsyncUdpSocket())
|
|
||||||
}
|
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
socket.close()
|
socket.close()
|
||||||
}
|
}
|
||||||
@@ -56,7 +56,7 @@ extension UDPConnection: GCDAsyncUdpSocketDelegate {
|
|||||||
fromAddress address: Data,
|
fromAddress address: Data,
|
||||||
withFilterContext filterContext: Any?) {
|
withFilterContext filterContext: Any?) {
|
||||||
|
|
||||||
let hostString = ipAddress(fromSockAddrData: address)!
|
let hostString = InternetProtocol.ipAddress(fromSockAddrData: address)!
|
||||||
delegate?.udpConnection(self, receivedData: data, fromHost: hostString)
|
delegate?.udpConnection(self, receivedData: data, fromHost: hostString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import CocoaAsyncSocket
|
|||||||
class UDPConnectionDelegateTestingStub: UDPConnectionDelegate {
|
class UDPConnectionDelegateTestingStub: UDPConnectionDelegate {
|
||||||
|
|
||||||
var receivedDataCalled = false
|
var receivedDataCalled = false
|
||||||
var receivedDataParameters: (sender: UDPConnection, data: Data, host: String)?
|
var receivedDataParameters: (sender: UDPConnectionProtocol, data: Data, host: String)?
|
||||||
func udpConnection(_ sender: UDPConnection, receivedData data: Data, fromHost host: String) {
|
func udpConnection(_ sender: UDPConnectionProtocol, receivedData data: Data, fromHost host: String) {
|
||||||
receivedDataCalled = true
|
receivedDataCalled = true
|
||||||
receivedDataParameters = (sender, data, host)
|
receivedDataParameters = (sender, data, host)
|
||||||
}
|
}
|
||||||
@@ -66,7 +66,7 @@ class UDPConnectionTests: XCTestCase {
|
|||||||
sut.udpSocket(socket, didReceive: packetData, fromAddress: addressData, withFilterContext: nil)
|
sut.udpSocket(socket, didReceive: packetData, fromAddress: addressData, withFilterContext: nil)
|
||||||
|
|
||||||
XCTAssert(delegate.receivedDataCalled)
|
XCTAssert(delegate.receivedDataCalled)
|
||||||
XCTAssertEqual(delegate.receivedDataParameters?.sender, sut)
|
XCTAssert(delegate.receivedDataParameters?.sender as AnyObject === sut)
|
||||||
XCTAssertEqual(delegate.receivedDataParameters?.data, packetData)
|
XCTAssertEqual(delegate.receivedDataParameters?.data, packetData)
|
||||||
XCTAssertEqual(delegate.receivedDataParameters?.host, "127.0.0.1")
|
XCTAssertEqual(delegate.receivedDataParameters?.host, "127.0.0.1")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
//
|
||||||
|
// BEncodeTestMacros.swift
|
||||||
|
// BEncode
|
||||||
|
//
|
||||||
|
// Created by Ben Davis on 22/08/2016.
|
||||||
|
// Copyright © 2016 bendavisapps. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
|
||||||
|
enum BEncoderTestError: Error {
|
||||||
|
case InvalidType
|
||||||
|
}
|
||||||
|
|
||||||
|
public func XCTAssertEqual<T>(_ expression1: @autoclosure () throws -> [[T]],
|
||||||
|
_ expression2: @autoclosure () throws -> [[T]],
|
||||||
|
_ message: @autoclosure () -> String = "",
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line) {
|
||||||
|
let array1: [[T]] = try! expression1()
|
||||||
|
let array2: [[T]] = try! expression2()
|
||||||
|
XCTAssertEqual(array1.count, array2.count)
|
||||||
|
for i in 0..<array1.count {
|
||||||
|
let element1 = array1[i]
|
||||||
|
let element2 = array2[i]
|
||||||
|
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func XCTAssertEqual<T>(_ expression1: @autoclosure () throws -> [T],
|
||||||
|
_ expression2: @autoclosure () throws -> [T],
|
||||||
|
_ message: @autoclosure () -> String = "",
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line) {
|
||||||
|
let array1: [T] = try! expression1()
|
||||||
|
let array2: [T] = try! expression2()
|
||||||
|
XCTAssertEqual(array1.count, array2.count)
|
||||||
|
for i in 0..<array1.count {
|
||||||
|
let element1 = array1[i]
|
||||||
|
let element2 = array2[i]
|
||||||
|
|
||||||
|
if let element1 = element1 as? Int, let element2 = element2 as? Int {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? String, let element2 = element2 as? String {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? Data, let element2 = element2 as? Data {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [T], let element2 = element2 as? [T] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [String:T], let element2 = element2 as? [String:T] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [Data:T], let element2 = element2 as? [Data:T] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public func XCTAssertEqual<T, E>(_ expression1: @autoclosure () throws -> [E: T],
|
||||||
|
_ expression2: @autoclosure () throws -> [E: T],
|
||||||
|
_ message: @autoclosure () -> String = "",
|
||||||
|
file: StaticString = #file,
|
||||||
|
line: UInt = #line) {
|
||||||
|
let dictionary1: [E: T] = try! expression1()
|
||||||
|
let dictionary2: [E: T] = try! expression2()
|
||||||
|
XCTAssertEqual(dictionary1.count, dictionary2.count)
|
||||||
|
|
||||||
|
let keys1 = [E](dictionary1.keys)
|
||||||
|
let keys2 = [E](dictionary2.keys)
|
||||||
|
XCTAssertEqual(keys1, keys2)
|
||||||
|
|
||||||
|
for key in keys1 {
|
||||||
|
let element1 = dictionary1[key]
|
||||||
|
let element2 = dictionary2[key]
|
||||||
|
|
||||||
|
if let element1 = element1 as? Int, let element2 = element2 as? Int {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? String, let element2 = element2 as? String {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? Data, let element2 = element2 as? Data {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [T], let element2 = element2 as? [T] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [String:T], let element2 = element2 as? [String:T] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [Data:T], let element2 = element2 as? [Data:T] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func XCTAssertEqual(_ element1: Any, element2: Any) throws {
|
||||||
|
if let element1 = element1 as? Int, let element2 = element2 as? Int {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? String, let element2 = element2 as? String {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? Data, let element2 = element2 as? Data {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [Any], let element2 = element2 as? [Any] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [String:Any], let element2 = element2 as? [String:Any] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else if let element1 = element1 as? [Data:Any], let element2 = element2 as? [Data:Any] {
|
||||||
|
XCTAssertEqual(element1, element2)
|
||||||
|
} else {
|
||||||
|
throw BEncoderTestError.InvalidType
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
Submodule Submodules/BEncodeSwift updated: b4036e2e33...7e104ab912
Reference in New Issue
Block a user