From b677eae167dbec1c922cdb02363f2bcde0a61238 Mon Sep 17 00:00:00 2001 From: Ben Davis Date: Tue, 4 Jul 2017 20:34:49 +0200 Subject: [PATCH] Implemented a UDPConnection class as a wrapper around a UDP implementation --- BitTorrent.xcodeproj/project.pbxproj | 28 +++ BitTorrent/BitTorrent-Bridging-Header.h | 12 ++ .../GCDAsyncUdpSocketStub.swift | 58 ++++++ BitTorrent/UDP Networking/InternetProtocol.h | 21 ++ BitTorrent/UDP Networking/InternetProtocol.m | 105 ++++++++++ .../UDP Networking/InternetProtocol.swift | 197 ++++++++++++++++++ .../InternetProtocolTests.swift | 48 +++++ BitTorrent/UDP Networking/UDPConnection.swift | 62 ++++++ .../UDP Networking/UDPConnectionTests.swift | 92 ++++++++ BitTorrentExample/AppDelegate.swift | 24 ++- Podfile | 4 +- Podfile.lock | 8 +- 12 files changed, 649 insertions(+), 10 deletions(-) create mode 100644 BitTorrent/BitTorrent-Bridging-Header.h create mode 100644 BitTorrent/UDP Networking/GCDAsyncUdpSocketStub.swift create mode 100644 BitTorrent/UDP Networking/InternetProtocol.h create mode 100644 BitTorrent/UDP Networking/InternetProtocol.m create mode 100644 BitTorrent/UDP Networking/InternetProtocol.swift create mode 100644 BitTorrent/UDP Networking/InternetProtocolTests.swift create mode 100644 BitTorrent/UDP Networking/UDPConnection.swift create mode 100644 BitTorrent/UDP Networking/UDPConnectionTests.swift diff --git a/BitTorrent.xcodeproj/project.pbxproj b/BitTorrent.xcodeproj/project.pbxproj index dd41abd..d1c52ce 100644 --- a/BitTorrent.xcodeproj/project.pbxproj +++ b/BitTorrent.xcodeproj/project.pbxproj @@ -10,6 +10,8 @@ 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 */; }; + B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */; }; + B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50B24F81F0A554A00C23E7C /* UDPConnection.swift */; }; B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B537CF051F03148B0084089B /* HTTPConnectionStub.swift */; }; B537CF461F031AD20084089B /* TestText.torrent in Resources */ = {isa = PBXBuildFile; fileRef = B5E9B0E31F02F9E700EF58E3 /* TestText.torrent */; }; B537CF491F031C3D0084089B /* BEncode.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B54D0C1A1CA53983004343BD /* BEncode.framework */; }; @@ -25,6 +27,9 @@ B5530DB51F03063E00F71CCD /* HTTPConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5530DB41F03063E00F71CCD /* HTTPConnectionTests.swift */; }; B55317DC1F02FC4D00909ADF /* TorrentHTTPTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55317DB1F02FC4D00909ADF /* TorrentHTTPTracker.swift */; }; B55317E01F02FE1500909ADF /* URLEncodeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B55317DF1F02FE1500909ADF /* URLEncodeTests.swift */; }; + B556D5AE1F0BA1FD00277B8D /* GCDAsyncUdpSocketStub.swift in Sources */ = {isa = PBXBuildFile; fileRef = B551C9541F0B9D3D004115CB /* GCDAsyncUdpSocketStub.swift */; }; + B558F4831F0A647D00438BB4 /* InternetProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B558F4821F0A647D00438BB4 /* InternetProtocol.swift */; }; + B558F4851F0A73D000438BB4 /* InternetProtocolTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B558F4841F0A73D000438BB4 /* InternetProtocolTests.swift */; }; B56A8A071C83539300426AC8 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56A8A061C83539300426AC8 /* TestHelpers.swift */; }; 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 */; }; @@ -112,6 +117,8 @@ 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; }; + B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnectionTests.swift; sourceTree = ""; }; + B50B24F81F0A554A00C23E7C /* UDPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UDPConnection.swift; sourceTree = ""; }; B537CF051F03148B0084089B /* HTTPConnectionStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnectionStub.swift; path = "HTTP Networking/HTTPConnectionStub.swift"; sourceTree = ""; }; B54D0C141CA53983004343BD /* BEncode.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = BEncode.xcodeproj; path = Submodules/BEncodeSwift/BEncode.xcodeproj; sourceTree = ""; }; B54D0C231CA56A22004343BD /* TorrentMetaInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TorrentMetaInfo.swift; path = Models/TorrentMetaInfo.swift; sourceTree = ""; }; @@ -123,10 +130,13 @@ B54D0C721CA5879E004343BD /* iphoneos.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = iphoneos.modulemap; sourceTree = ""; }; B54D0C731CA587A9004343BD /* iphonesimulator.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = iphonesimulator.modulemap; sourceTree = ""; }; B54D0C741CA587B5004343BD /* macosx.modulemap */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = "sourcecode.module-map"; path = macosx.modulemap; sourceTree = ""; }; + B551C9541F0B9D3D004115CB /* GCDAsyncUdpSocketStub.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GCDAsyncUdpSocketStub.swift; sourceTree = ""; }; B5530DB11F03063300F71CCD /* HTTPConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnection.swift; path = "HTTP Networking/HTTPConnection.swift"; sourceTree = ""; }; B5530DB41F03063E00F71CCD /* HTTPConnectionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = HTTPConnectionTests.swift; path = "HTTP Networking/HTTPConnectionTests.swift"; sourceTree = ""; }; B55317DB1F02FC4D00909ADF /* TorrentHTTPTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TorrentHTTPTracker.swift; path = Tracker/TorrentHTTPTracker.swift; sourceTree = ""; }; B55317DF1F02FE1500909ADF /* URLEncodeTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = URLEncodeTests.swift; path = "HTTP Networking/URLEncodeTests.swift"; sourceTree = ""; }; + B558F4821F0A647D00438BB4 /* InternetProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetProtocol.swift; sourceTree = ""; }; + B558F4841F0A73D000438BB4 /* InternetProtocolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InternetProtocolTests.swift; sourceTree = ""; }; B56A8A061C83539300426AC8 /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestHelpers.swift; path = Utilities/TestHelpers.swift; sourceTree = ""; }; B585AB741C3833450093FA41 /* BitTorrent.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = BitTorrent.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B585AB771C3833450093FA41 /* BitTorrent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BitTorrent.h; sourceTree = ""; }; @@ -204,6 +214,18 @@ name = Pods; sourceTree = ""; }; + B50B24F01F0A54C100C23E7C /* UDP Networking */ = { + isa = PBXGroup; + children = ( + B558F4821F0A647D00438BB4 /* InternetProtocol.swift */, + B558F4841F0A73D000438BB4 /* InternetProtocolTests.swift */, + B50B24F81F0A554A00C23E7C /* UDPConnection.swift */, + B551C9541F0B9D3D004115CB /* GCDAsyncUdpSocketStub.swift */, + B50B24F61F0A553B00C23E7C /* UDPConnectionTests.swift */, + ); + path = "UDP Networking"; + sourceTree = ""; + }; B54D0C131CA53979004343BD /* Frameworks */ = { isa = PBXGroup; children = ( @@ -306,6 +328,7 @@ B54D0C251CA56A25004343BD /* Models */, B54D0C291CA5785F004343BD /* Utilities */, B5E9778F1CAFB2090038EBE7 /* HTTP Networking */, + B50B24F01F0A54C100C23E7C /* UDP Networking */, B585AB791C3833450093FA41 /* Info.plist */, ); path = BitTorrent; @@ -712,10 +735,12 @@ B5F81E491F0436D600B25C70 /* TorrentPeer.swift in Sources */, B55317DC1F02FC4D00909ADF /* TorrentHTTPTracker.swift in Sources */, B5E977961CAFB46B0038EBE7 /* String+URLEncode.swift in Sources */, + B558F4831F0A647D00438BB4 /* InternetProtocol.swift in Sources */, B54D0C7A1CA69FAD004343BD /* TorrentMetaInfo.swift in Sources */, B5F81E4B1F04399800B25C70 /* TorrentTrackerResponse.swift in Sources */, B54D0C7B1CA69FD8004343BD /* Data+sha1.swift in Sources */, B5530DB21F03063300F71CCD /* HTTPConnection.swift in Sources */, + B50B24F91F0A554A00C23E7C /* UDPConnection.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -725,11 +750,14 @@ files = ( B537CF061F03148B0084089B /* HTTPConnectionStub.swift in Sources */, B5BD7FD61F03032400621BC2 /* TorrentHTTPTrackerTests.swift in Sources */, + B556D5AE1F0BA1FD00277B8D /* GCDAsyncUdpSocketStub.swift in Sources */, B5530DB51F03063E00F71CCD /* HTTPConnectionTests.swift in Sources */, B56A8A071C83539300426AC8 /* TestHelpers.swift in Sources */, B585AB841C3833450093FA41 /* BitTorrentTests.swift in Sources */, B54D0C271CA56ADB004343BD /* TorrentMetaInfoTests.swift in Sources */, B55317E01F02FE1500909ADF /* URLEncodeTests.swift in Sources */, + B558F4851F0A73D000438BB4 /* InternetProtocolTests.swift in Sources */, + B50B24F71F0A553F00C23E7C /* UDPConnectionTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/BitTorrent/BitTorrent-Bridging-Header.h b/BitTorrent/BitTorrent-Bridging-Header.h new file mode 100644 index 0000000..dd7dff4 --- /dev/null +++ b/BitTorrent/BitTorrent-Bridging-Header.h @@ -0,0 +1,12 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + +#import +#import "GCDAsyncSocket.h" +#import "GCDAsyncUdpSocket.h" +#import "InternetProtocol.h" +#include +#include +#include +#include \ No newline at end of file diff --git a/BitTorrent/UDP Networking/GCDAsyncUdpSocketStub.swift b/BitTorrent/UDP Networking/GCDAsyncUdpSocketStub.swift new file mode 100644 index 0000000..30c518d --- /dev/null +++ b/BitTorrent/UDP Networking/GCDAsyncUdpSocketStub.swift @@ -0,0 +1,58 @@ +// +// GCDAsyncUdpSocketProtocol.swift +// BitTorrent +// +// Created by Ben Davis on 04/07/2017. +// Copyright © 2017 Ben Davis. All rights reserved. +// + +import Foundation +import CocoaAsyncSocket + +class GCDAsyncUdpSocketStub: GCDAsyncUdpSocket { + + weak var _delegate: GCDAsyncUdpSocketDelegate? + + override func delegate() -> GCDAsyncUdpSocketDelegate? { + return _delegate + } + + override func setDelegate(_ delegate: GCDAsyncUdpSocketDelegate?) { + _delegate = delegate + } + + var bindToPortCalled = false + var bindToPortParameter: UInt16? + override func bind(toPort port: UInt16) throws { + bindToPortCalled = true + bindToPortParameter = port + } + + var beginReceivingCalled = false + override func beginReceiving() throws { + beginReceivingCalled = true + } + + var _delegateQueue: DispatchQueue? + + override func delegateQueue() -> DispatchQueue? { + return _delegateQueue + } + + override func synchronouslySetDelegateQueue(_ delegateQueue: DispatchQueue?) { + _delegateQueue = delegateQueue + } + + var closeCalled = false + override func close() { + closeCalled = true + } + + var sendCalled = false + var sendParameters: (data: Data, host: String, port: UInt16, timeout: TimeInterval, tag: Int)? + override func send(_ data: Data, toHost host: String, port: UInt16, withTimeout timeout: TimeInterval, tag: Int) { + sendCalled = true + sendParameters = (data, host, port, timeout, tag) + } +} + diff --git a/BitTorrent/UDP Networking/InternetProtocol.h b/BitTorrent/UDP Networking/InternetProtocol.h new file mode 100644 index 0000000..b5f7e6f --- /dev/null +++ b/BitTorrent/UDP Networking/InternetProtocol.h @@ -0,0 +1,21 @@ +// +// InternetProtocol.h +// BitTorrent +// +// Created by Ben Davis on 16/11/2014. +// Copyright (c) 2014 Ben Davis. All rights reserved. +// + +#import + +@interface InternetProtocol : NSObject + ++ (NSString *)getIPAddress:(BOOL)preferIPv4; ++ (NSDictionary *)getIPAddresses; + ++ (NSString*)ipAddressOfHostname:(NSString*)hostName; + ++ (NSString*)ipAddressFromSockAddrData:(NSData*)data; ++ (uint16_t)portFromSockAddrData:(NSData*)data; + +@end diff --git a/BitTorrent/UDP Networking/InternetProtocol.m b/BitTorrent/UDP Networking/InternetProtocol.m new file mode 100644 index 0000000..50a7fdd --- /dev/null +++ b/BitTorrent/UDP Networking/InternetProtocol.m @@ -0,0 +1,105 @@ +// +// InternetProtocol.m +// BitTorrent +// +// Created by Ben Davis on 16/11/2014. +// Copyright (c) 2014 Ben Davis. All rights reserved. +// + +#import "InternetProtocol.h" +#include +#include +#include +#include + +#define IOS_CELLULAR @"pdp_ip0" +#define IOS_WIFI @"en0" +#define IP_ADDR_IPv4 @"ipv4" +#define IP_ADDR_IPv6 @"ipv6" + +@implementation InternetProtocol + ++ (NSString *)getIPAddress:(BOOL)preferIPv4 { + NSArray *searchArray = preferIPv4 ? + @[ IOS_WIFI @"/" IP_ADDR_IPv4, IOS_WIFI @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6 ] : + @[ IOS_WIFI @"/" IP_ADDR_IPv6, IOS_WIFI @"/" IP_ADDR_IPv4, IOS_CELLULAR @"/" IP_ADDR_IPv6, IOS_CELLULAR @"/" IP_ADDR_IPv4 ] ; + + NSDictionary *addresses = [self getIPAddresses]; + NSLog(@"addresses: %@", addresses); + + __block NSString *address; + [searchArray enumerateObjectsUsingBlock:^(NSString *key, NSUInteger idx, BOOL *stop) + { + address = addresses[key]; + if(address) *stop = YES; + } ]; + return address ? address : @"0.0.0.0"; +} + ++ (NSDictionary *)getIPAddresses { + NSMutableDictionary *addresses = [NSMutableDictionary dictionaryWithCapacity:8]; + + // retrieve the current interfaces - returns 0 on success + struct ifaddrs *interfaces; + if(!getifaddrs(&interfaces)) { + // Loop through linked list of interfaces + struct ifaddrs *interface; + for(interface=interfaces; interface; interface=interface->ifa_next) { + if(!(interface->ifa_flags & IFF_UP) /* || (interface->ifa_flags & IFF_LOOPBACK) */ ) { + continue; // deeply nested code harder to read + } + const struct sockaddr_in *addr = (const struct sockaddr_in*)interface->ifa_addr; + char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; + if(addr && (addr->sin_family==AF_INET || addr->sin_family==AF_INET6)) { + NSString *name = [NSString stringWithUTF8String:interface->ifa_name]; + NSString *type; + if(addr->sin_family == AF_INET) { + if(inet_ntop(AF_INET, &addr->sin_addr, addrBuf, INET_ADDRSTRLEN)) { + type = IP_ADDR_IPv4; + } + } else { + const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6*)interface->ifa_addr; + if(inet_ntop(AF_INET6, &addr6->sin6_addr, addrBuf, INET6_ADDRSTRLEN)) { + type = IP_ADDR_IPv6; + } + } + if(type) { + NSString *key = [NSString stringWithFormat:@"%@/%@", name, type]; + addresses[key] = [NSString stringWithUTF8String:addrBuf]; + } + } + } + // Free memory + freeifaddrs(interfaces); + } + return [addresses count] ? addresses : nil; +} + ++(NSString *)ipAddressOfHostname:(NSString *)hostName { + struct hostent *host_entry = gethostbyname([hostName cStringUsingEncoding:NSASCIIStringEncoding]); + char *buff; + if (host_entry != NULL) { + buff = inet_ntoa(*((struct in_addr *)host_entry->h_addr_list[0])); + NSString *ip = [NSString stringWithUTF8String:buff]; + return ip; + } else { + return nil; + } +} + ++ (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; +} + +@end diff --git a/BitTorrent/UDP Networking/InternetProtocol.swift b/BitTorrent/UDP Networking/InternetProtocol.swift new file mode 100644 index 0000000..9cc1adb --- /dev/null +++ b/BitTorrent/UDP Networking/InternetProtocol.swift @@ -0,0 +1,197 @@ +// +// InternetProtocol.swift +// BitTorrent +// +// Created by Ben Davis on 03/07/2017. +// Copyright © 2017 Ben Davis. All rights reserved. +// + +import Foundation + +let IOS_CELLULAR_INTERFACE_NAME = "pdp_ip0" +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) 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) 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), + let hostEntry = gethostbyname(hostnameCString)?.pointee, + let hostAddressList = hostEntry.h_addr_list?.pointee else { + return nil + } + + let firstHostAddress = hostAddressList.withMemoryRebound(to: in_addr.self, capacity: 1) { $0.pointee } + let firstHostAddressCString = inet_ntoa(firstHostAddress)! + return String(cString: firstHostAddressCString) +} + +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 + } + } + + return nil +} + +func getLocalIPAddresses() -> [String: String] { + + var addresses: [String: String] = [:] + + // Get list of all network interfaces on the local machine: + var ifaddrsPointer : UnsafeMutablePointer? + 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 typealias Element = ifaddrs + + var nextElement: ifaddrs? + + init(first: ifaddrs) { + nextElement = first + } + + public func next() -> ifaddrs? { + let result = nextElement + nextElement = result?.ifa_next?.pointee + return result + } +} + +extension ifaddrs: Sequence { + public func makeIterator() -> ifaddrsIterator { + return ifaddrsIterator(first: self) + } +} + +extension ifaddrs { + + var name: String { + return String(cString: ifa_name) + } + + var isIpv4: Bool { + let addr = ifa_addr.pointee + return addr.sa_family == UInt8(AF_INET) + } + + var isIpv6: Bool { + let addr = ifa_addr.pointee + return addr.sa_family == UInt8(AF_INET6) + } + + var isUpAndRunning: Bool { + let flags = Int32(ifa_flags) + let upAndRunningFlags = (IFF_UP|IFF_RUNNING) + return (flags & upAndRunningFlags) == upAndRunningFlags + } + + var isLoopbackNet: Bool { + let flags = Int32(ifa_flags) + return (flags & IFF_LOOPBACK) == IFF_LOOPBACK + } + + func convertToIPString() -> String? { + return ifa_addr.pointee.toString() + } + + func nameAndTypeString() -> String? { + + guard isIpv4 || isIpv6 else { + return nil + } + + return "\(name) - " + (isIpv4 ? IP_ADDR_IPv4_INTERFACE_NAME : IP_ADDR_IPv6_INTERFACE_NAME) + } +} + +extension sockaddr { + + func toString() -> String? { + + var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST)) + var addr = self + + guard getnameinfo(&addr, + socklen_t(addr.sa_len), + &hostname, + socklen_t(hostname.count), + nil, + socklen_t(0), + NI_NUMERICHOST) == 0 else { return nil } + + return String(validatingUTF8: hostname) + } +} diff --git a/BitTorrent/UDP Networking/InternetProtocolTests.swift b/BitTorrent/UDP Networking/InternetProtocolTests.swift new file mode 100644 index 0000000..7d60600 --- /dev/null +++ b/BitTorrent/UDP Networking/InternetProtocolTests.swift @@ -0,0 +1,48 @@ +// +// InternetProtocolTests.swift +// BitTorrentTests +// +// Created by Ben Davis on 03/07/2017. +// Copyright © 2017 Ben Davis. All rights reserved. +// + +import XCTest +@testable import BitTorrent + +class InternetProtocolTests: XCTestCase { + + func test_canDecodeLocalhost() { + let result = getIPAddress(of: "localhost") + XCTAssertNotNil(result) + XCTAssertEqual(result!, "127.0.0.1") + } + + func test_invalidHostnameReturnsNil() { + let result = getIPAddress(of: "asldfjhablskhdbj") + XCTAssertNil(result) + } + + func test_nonAsciiHostnameReturnsNil() { + let result = getIPAddress(of: "🙁") + XCTAssertNil(result) + } + + func test_canDecodeGoogle() { + let result = getIPAddress(of: "google.com") + XCTAssertNotNil(result) + } + + func test_canDecodeIPv4AddressFromData() { + 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) + XCTAssertNotNil(result) + XCTAssertEqual(result, "127.0.0.1") + } + + func test_canDecodeSocketPortFromData() { + 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) + XCTAssertNotNil(result) + XCTAssertEqual(result, 27002) + } +} diff --git a/BitTorrent/UDP Networking/UDPConnection.swift b/BitTorrent/UDP Networking/UDPConnection.swift new file mode 100644 index 0000000..8c7ed1f --- /dev/null +++ b/BitTorrent/UDP Networking/UDPConnection.swift @@ -0,0 +1,62 @@ +// +// UDPConnection.swift +// BitTorrent +// +// Created by Ben Davis on 03/07/2017. +// Copyright © 2017 Ben Davis. All rights reserved. +// + +import Foundation +import CocoaAsyncSocket + +protocol UDPConnectionDelegate: class { + func udpConnection(_ sender: UDPConnection, receivedData data: Data, fromHost host: String) +} + +/// 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 +/// one day. +class UDPConnection: NSObject { + + weak var delegate: UDPConnectionDelegate? + + private let socket: GCDAsyncUdpSocket + + // Designated init for testing + init(socket: GCDAsyncUdpSocket) { + self.socket = socket + super.init() + socket.setDelegate(self) + socket.synchronouslySetDelegateQueue(.main) + } + + // Useful init which should be used + override convenience init() { + self.init(socket: GCDAsyncUdpSocket()) + } + + deinit { + socket.close() + } + + func startListening(on port: UInt16) { + try? socket.bind(toPort: port) + try? socket.beginReceiving() + } + + func send(_ data: Data, toHost host: String, port: UInt16, timeout: TimeInterval) { + socket.send(data, toHost: host, port: port, withTimeout: timeout, tag: 0) + } +} + +extension UDPConnection: GCDAsyncUdpSocketDelegate { + + func udpSocket(_ sock: GCDAsyncUdpSocket, + didReceive data: Data, + fromAddress address: Data, + withFilterContext filterContext: Any?) { + + let hostString = ipAddress(fromSockAddrData: address)! + delegate?.udpConnection(self, receivedData: data, fromHost: hostString) + } +} diff --git a/BitTorrent/UDP Networking/UDPConnectionTests.swift b/BitTorrent/UDP Networking/UDPConnectionTests.swift new file mode 100644 index 0000000..bd3eddc --- /dev/null +++ b/BitTorrent/UDP Networking/UDPConnectionTests.swift @@ -0,0 +1,92 @@ +// +// UDPConnectionTests.swift +// BitTorrentTests +// +// Created by Ben Davis on 03/07/2017. +// Copyright © 2017 Ben Davis. All rights reserved. +// + +import XCTest +import CocoaAsyncSocket +@testable import BitTorrent + +class UDPConnectionDelegateTestingStub: UDPConnectionDelegate { + + var receivedDataCalled = false + var receivedDataParameters: (sender: UDPConnection, data: Data, host: String)? + func udpConnection(_ sender: UDPConnection, receivedData data: Data, fromHost host: String) { + receivedDataCalled = true + receivedDataParameters = (sender, data, host) + } +} + +class UDPConnectionTests: XCTestCase { + + var socket: GCDAsyncUdpSocketStub! + var delegate: UDPConnectionDelegateTestingStub! + var sut: UDPConnection! + + override func setUp() { + super.setUp() + + socket = GCDAsyncUdpSocketStub() + delegate = UDPConnectionDelegateTestingStub() + sut = UDPConnection(socket: socket) + sut.delegate = delegate + } + + func test_isSocketDelegate() { + XCTAssert(socket._delegate === sut) + XCTAssert(socket._delegateQueue === DispatchQueue.main) + } + + func test_canStartListeningOnPort() { + + let port: UInt16 = 123 + sut.startListening(on: port) + + XCTAssert(socket.bindToPortCalled) + XCTAssertEqual(socket.bindToPortParameter, port) + XCTAssert(socket.beginReceivingCalled) + } + + func test_socketClosedOnDeinit() { + sut = nil + XCTAssert(socket.closeCalled) + } + + // MARK: - Receiving data + + func test_canRecieveData() { + + // 127.0.0.1:27002 + let addressData = Data(bytes: [16,2,122,105,127,0,0,1,0,0,0,0,0,0,0,0]) + let packetData = Data(bytes: [1,2,3]) + + sut.udpSocket(socket, didReceive: packetData, fromAddress: addressData, withFilterContext: nil) + + XCTAssert(delegate.receivedDataCalled) + XCTAssertEqual(delegate.receivedDataParameters?.sender, sut) + XCTAssertEqual(delegate.receivedDataParameters?.data, packetData) + XCTAssertEqual(delegate.receivedDataParameters?.host, "127.0.0.1") + } + + // MARK: - Sending data + + func test_canSendData() { + + let data = Data(bytes: [1,2,3]) + let host = "127.0.0.1" + let port: UInt16 = 3475 + let timeout: TimeInterval = 10 + + sut.send(data, toHost: host, port: port, timeout: timeout) + + XCTAssert(socket.sendCalled) + XCTAssertEqual(socket.sendParameters!.data, data) + XCTAssertEqual(socket.sendParameters!.host, host) + XCTAssertEqual(socket.sendParameters!.port, port) + XCTAssertEqual(socket.sendParameters!.timeout, timeout) + } +} + diff --git a/BitTorrentExample/AppDelegate.swift b/BitTorrentExample/AppDelegate.swift index 4458bf0..e355a38 100644 --- a/BitTorrentExample/AppDelegate.swift +++ b/BitTorrentExample/AppDelegate.swift @@ -8,14 +8,14 @@ import UIKit @testable import BitTorrent -import BlueSocket @UIApplicationMain -class AppDelegate: UIResponder, UIApplicationDelegate { +class AppDelegate: UIResponder, UIApplicationDelegate, UDPConnectionDelegate { var window: UIWindow? var tracker: TorrentHTTPTracker! + var udpConnection: UDPConnection! func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { window = UIWindow(frame: UIScreen.main.bounds) @@ -39,10 +39,26 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // numberOfBytesDownloaded: 0, // numberOfPeersToFetch: 50) - let mySocket = try! Socket.create() - print(mySocket) +// let mySocket = try! Socket.create() +// print(mySocket) + + udpConnection = UDPConnection() + udpConnection.delegate = self + + udpConnection.startListening(on: 59740) + + let data = "Hello, world!".data(using: .utf8)! + udpConnection.send(data, toHost: "127.0.0.1", port: 31337, timeout: 10000) return true } } + +extension UDPConnectionDelegate { + + func udpConnection(_ sender: UDPConnection, receivedData data: Data, fromHost host: String) { + print(sender, data, host) + print(data == "Hello, world!".data(using: .utf8)! ? "Success" : "Fail") + } +} diff --git a/Podfile b/Podfile index 334f635..a46d2b8 100644 --- a/Podfile +++ b/Podfile @@ -4,7 +4,7 @@ target 'BitTorrent' do use_frameworks! pod 'Alamofire', '4.5.0' - pod 'BlueSocket' + pod 'CocoaAsyncSocket' target 'BitTorrentTests' do inherit! :search_paths @@ -15,7 +15,7 @@ target 'BitTorrent' do target 'BitTorrentExample' do use_frameworks! - + end end diff --git a/Podfile.lock b/Podfile.lock index 208b739..d043f61 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,6 +1,6 @@ PODS: - Alamofire (4.5.0) - - BlueSocket (0.12.16) + - CocoaAsyncSocket (7.6.1) - OHHTTPStubs (6.0.0): - OHHTTPStubs/Default (= 6.0.0) - OHHTTPStubs/Core (6.0.0) @@ -17,14 +17,14 @@ PODS: DEPENDENCIES: - Alamofire (= 4.5.0) - - BlueSocket + - CocoaAsyncSocket - OHHTTPStubs SPEC CHECKSUMS: Alamofire: f28cdffd29de33a7bfa022cbd63ae95a27fae140 - BlueSocket: 13bf1daf94a316e9efe93aa125cc33b8c3dd6c35 + CocoaAsyncSocket: 7eadd3f59e1a6c84e2aefc93e9ff7b55156fe174 OHHTTPStubs: 752f9b11fd810a15162d50f11c06ff94f8e012eb -PODFILE CHECKSUM: 0d38beb67a05d0be9e85d2555a9fbc44ecf5e80c +PODFILE CHECKSUM: 0357d6a70a8533160f506a62590771b1f36ca2cc COCOAPODS: 1.2.0