SimpleTunnelServices/util.swift
/* |
Copyright (C) 2016 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
This file contains some utility classes and functions used by various parts of the SimpleTunnel project. |
*/ |
import Foundation |
import Darwin |
/// SimpleTunnel errors |
public enum SimpleTunnelError: Error { |
case badConfiguration |
case badConnection |
case internalError |
} |
/// A queue of blobs of data |
class SavedData { |
// MARK: Properties |
/// Each item in the list contains a data blob and an offset (in bytes) within the data blob of the data that is yet to be written. |
var chain = [(data: Data, offset: Int)]() |
/// A convenience property to determine if the list is empty. |
var isEmpty: Bool { |
return chain.isEmpty |
} |
// MARK: Interface |
/// Add a data blob and offset to the end of the list. |
func append(_ data: Data, offset: Int) { |
chain.append(data: data, offset: offset) |
} |
/// Write as much of the data in the list as possible to a stream |
func writeToStream(_ stream: OutputStream) -> Bool { |
var result = true |
var stopIndex: Int? |
for (chainIndex, record) in chain.enumerated() { |
let written = writeData(record.data, toStream: stream, startingAtOffset:record.offset) |
if written < 0 { |
result = false |
break |
} |
if written < (record.data.count - record.offset) { |
// Failed to write all of the remaining data in this blob, update the offset. |
chain[chainIndex] = (record.data, record.offset + written) |
stopIndex = chainIndex |
break |
} |
} |
if let removeEnd = stopIndex { |
// We did not write all of the data, remove what was written. |
if removeEnd > 0 { |
chain.removeSubrange(0..<removeEnd) |
} |
} else { |
// All of the data was written. |
chain.removeAll(keepingCapacity: false) |
} |
return result |
} |
/// Remove all data from the list. |
func clear() { |
chain.removeAll(keepingCapacity: false) |
} |
} |
/// A object containing a sockaddr_in6 structure. |
class SocketAddress6 { |
// MARK: Properties |
/// The sockaddr_in6 structure. |
var sin6: sockaddr_in6 |
/// The IPv6 address as a string. |
var stringValue: String? { |
return withUnsafePointer(to: &sin6) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { saToString($0) } } |
} |
// MARK: Initializers |
init() { |
sin6 = sockaddr_in6() |
sin6.sin6_len = __uint8_t(MemoryLayout<sockaddr_in6>.size) |
sin6.sin6_family = sa_family_t(AF_INET6) |
sin6.sin6_port = in_port_t(0) |
sin6.sin6_addr = in6addr_any |
sin6.sin6_scope_id = __uint32_t(0) |
sin6.sin6_flowinfo = __uint32_t(0) |
} |
convenience init(otherAddress: SocketAddress6) { |
self.init() |
sin6 = otherAddress.sin6 |
} |
/// Set the IPv6 address from a string. |
func setFromString(_ str: String) -> Bool { |
return str.withCString({ cs in inet_pton(AF_INET6, cs, &sin6.sin6_addr) }) == 1 |
} |
/// Set the port. |
func setPort(_ port: Int) { |
sin6.sin6_port = in_port_t(UInt16(port).bigEndian) |
} |
} |
/// An object containing a sockaddr_in structure. |
class SocketAddress { |
// MARK: Properties |
/// The sockaddr_in structure. |
var sin: sockaddr_in |
/// The IPv4 address in string form. |
var stringValue: String? { |
return withUnsafePointer(to: &sin) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { saToString($0) } } |
} |
// MARK: Initializers |
init() { |
sin = sockaddr_in(sin_len:__uint8_t(MemoryLayout<sockaddr_in>.size), sin_family:sa_family_t(AF_INET), sin_port:in_port_t(0), sin_addr:in_addr(s_addr: 0), sin_zero:(Int8(0), Int8(0), Int8(0), Int8(0), Int8(0), Int8(0), Int8(0), Int8(0))) |
} |
convenience init(otherAddress: SocketAddress) { |
self.init() |
sin = otherAddress.sin |
} |
/// Set the IPv4 address from a string. |
func setFromString(_ str: String) -> Bool { |
return str.withCString({ cs in inet_pton(AF_INET, cs, &sin.sin_addr) }) == 1 |
} |
/// Set the port. |
func setPort(_ port: Int) { |
sin.sin_port = in_port_t(UInt16(port).bigEndian) |
} |
/// Increment the address by a given amount. |
func increment(_ amount: UInt32) { |
let networkAddress = sin.sin_addr.s_addr.byteSwapped + amount |
sin.sin_addr.s_addr = networkAddress.byteSwapped |
} |
/// Get the difference between this address and another address. |
func difference(_ otherAddress: SocketAddress) -> Int64 { |
return Int64(sin.sin_addr.s_addr.byteSwapped - otherAddress.sin.sin_addr.s_addr.byteSwapped) |
} |
} |
// MARK: Utility Functions |
/// Convert a sockaddr structure to a string. |
func saToString(_ sa: UnsafePointer<sockaddr>) -> String? { |
var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST)) |
var portBuffer = [CChar](repeating: 0, count: Int(NI_MAXSERV)) |
guard getnameinfo(sa, socklen_t(sa.pointee.sa_len), &hostBuffer, socklen_t(hostBuffer.count), &portBuffer, socklen_t(portBuffer.count), NI_NUMERICHOST | NI_NUMERICSERV) == 0 |
else { return nil } |
return String(cString: hostBuffer) |
} |
/// Write a blob of data to a stream starting from a particular offset. |
func writeData(_ data: Data, toStream stream: OutputStream, startingAtOffset offset: Int) -> Int { |
var written = 0 |
var currentOffset = offset |
while stream.hasSpaceAvailable && currentOffset < data.count { |
let writeResult = stream.write((data as NSData).bytes.bindMemory(to: UInt8.self, capacity: data.count) + currentOffset, maxLength: data.count - currentOffset) |
guard writeResult >= 0 else { return writeResult } |
written += writeResult |
currentOffset += writeResult |
} |
return written |
} |
/// Create a SimpleTunnel protocol message dictionary. |
public func createMessagePropertiesForConnection(_ connectionIdentifier: Int, commandType: TunnelCommand, extraProperties: [String: AnyObject] = [:]) -> [String: AnyObject] { |
// Start out with the "extra properties" that the caller specified. |
var properties = extraProperties |
// Add in the standard properties common to all messages. |
properties[TunnelMessageKey.Identifier.rawValue] = connectionIdentifier as AnyObject? |
properties[TunnelMessageKey.Command.rawValue] = commandType.rawValue as AnyObject? |
return properties |
} |
/// Keys in the tunnel server configuration plist. |
public enum SettingsKey: String { |
case IPv4 = "IPv4" |
case DNS = "DNS" |
case Proxies = "Proxies" |
case Pool = "Pool" |
case StartAddress = "StartAddress" |
case EndAddress = "EndAddress" |
case Servers = "Servers" |
case SearchDomains = "SearchDomains" |
case Address = "Address" |
case Netmask = "Netmask" |
case Routes = "Routes" |
} |
/// Get a value from a plist given a list of keys. |
public func getValueFromPlist(_ plist: [NSObject: AnyObject], keyArray: [SettingsKey]) -> AnyObject? { |
var subPlist = plist |
for (index, key) in keyArray.enumerated() { |
if index == keyArray.count - 1 { |
return subPlist[key.rawValue as NSString] |
} |
else if let subSubPlist = subPlist[key.rawValue as NSString] as? [NSObject: AnyObject] { |
subPlist = subSubPlist |
} |
else { |
break |
} |
} |
return nil |
} |
/// Create a new range by incrementing the start of the given range by a given ammount. |
func rangeByMovingStartOfRange(_ range: Range<Int>, byCount: Int) -> CountableRange<Int> { |
return (range.lowerBound + byCount)..<range.upperBound |
} |
public func simpleTunnelLog(_ message: String) { |
NSLog(message) |
} |
Copyright © 2016 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2016-10-04