CFHostSampleSwift/main.swift
/* |
Copyright (C) 2017 Apple Inc. All Rights Reserved. |
See LICENSE.txt for this sample’s licensing information |
Abstract: |
Command line tool main. |
*/ |
import Foundation |
// MARK: - main object |
/// The main object, which is instantiated and run by the main function. |
/// |
/// We have to use an object here because the underlying `HostAddressQuery` |
/// and `HostNameQuery` classes need a delegate. |
class MainObj: HostAddressQueryDelegate, HostNameQueryDelegate { |
/// The name-to-address queries to run. |
var addressQueries: [HostAddressQuery] = [] |
/// The address-to-name queries to run. |
var nameQueries: [HostNameQuery] = [] |
/// The total number of queries to run. |
var queryCount: Int { return self.addressQueries.count + self.nameQueries.count } |
/// The number of queries that have finished. |
var finishedQueryCount = 0 |
/// Runs the queries, not returning until they're all finished. |
func run() { |
self.addressQueries.forEach( { |
$0.delegate = self |
$0.start() |
} ) |
self.nameQueries.forEach( { |
$0.delegate = self |
$0.start() |
} ) |
while self.finishedQueryCount != self.queryCount { |
RunLoop.current.run(mode: .defaultRunLoopMode, before: .distantFuture) |
} |
} |
func didComplete(addresses: [Data], hostAddressQuery query: HostAddressQuery) { |
let addressList = addresses.map { numeric(for: $0) }.joined(separator: ", ") |
print("\(query.name) -> \(addressList)", to: &Process.stdout) |
self.finishedQueryCount += 1 |
} |
func didComplete(error: Error, hostAddressQuery query: HostAddressQuery) { |
print("\(query.name) -> \(string(for: error))", to: &Process.stdout) |
self.finishedQueryCount += 1 |
} |
func didComplete(names: [String], hostNameQuery query: HostNameQuery) { |
let addressString = numeric(for: query.address) |
let nameList = names.joined(separator: ",") |
print("\(addressString) -> \(nameList)", to: &Process.stdout) |
self.finishedQueryCount += 1 |
} |
func didComplete(error: Error, hostNameQuery query: HostNameQuery) { |
let addressString = numeric(for: query.address) |
print("\(addressString) -> \(string(for: error))", to: &Process.stdout) |
self.finishedQueryCount += 1 |
} |
} |
// MARK: - utilities |
/// Returns a string for the supplied error, formatted to make sense as command line output. |
/// |
/// - Parameter error: The error to format. |
/// - Returns: A string for that error, formatted to make sense as command line output. |
func string(for error: Error) -> String { |
let error = error as NSError |
if error.domain == kCFErrorDomainCFNetwork as String { |
if let code = (error.userInfo[kCFGetAddrInfoFailureKey as String] as? NSNumber)?.int32Value { |
// We deliberately don't call `gai_strerror` here because this is a developer- |
// oriented tool and we want to show the symbolic name of the error. |
switch code { |
case EAI_ADDRFAMILY: return "EAI_ADDRFAMILY" |
case EAI_AGAIN: return "EAI_AGAIN" |
case EAI_BADFLAGS: return "EAI_BADFLAGS" |
case EAI_FAIL: return "EAI_FAIL" |
case EAI_FAMILY: return "EAI_FAMILY" |
case EAI_MEMORY: return "EAI_MEMORY" |
case EAI_NODATA: return "EAI_NODATA" |
case EAI_NONAME: return "EAI_NONAME" |
case EAI_SERVICE: return "EAI_SERVICE" |
case EAI_SOCKTYPE: return "EAI_SOCKTYPE" |
case EAI_SYSTEM: return "EAI_SYSTEM" |
case EAI_BADHINTS: return "EAI_BADHINTS" |
case EAI_PROTOCOL: return "EAI_PROTOCOL" |
case EAI_OVERFLOW: return "EAI_OVERFLOW" |
default: |
break |
} |
} |
} |
return error.description |
} |
/// Converts an IP address to its string equivalent. |
/// |
/// This does not hit the network and thus won't fail. |
/// |
/// - Parameter address: An IP address, as a `Data` value containing some flavour of `sockaddr`. |
/// - Returns: The string equivalent of that IP address. |
func numeric(for address: Data) -> String { |
var name = [CChar](repeating: 0, count: Int(NI_MAXHOST)) |
let saLen = socklen_t(address.count) |
let success = address.withUnsafeBytes { (sa: UnsafePointer<sockaddr>) in |
return getnameinfo(sa, saLen, &name, socklen_t(name.count), nil, 0, NI_NUMERICHOST | NI_NUMERICSERV) == 0 |
} |
guard success else { |
return "?" |
} |
return String(cString: name) |
} |
/// Converts an IP address string to an IP address. |
/// |
/// This does not hit the network but can fail if the string is not a valid IP address. |
/// |
/// - Parameter numeric: An IP address string. |
/// - Returns: An IP address, as a `Data` value containing some flavour of `sockaddr`, or nil. |
func data(for numeric: String) -> Data? { |
var hints = addrinfo() |
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV |
var addrList: UnsafeMutablePointer<addrinfo>? = nil |
let success = getaddrinfo(numeric, nil, &hints, &addrList) == 0 |
guard success else { |
return nil |
} |
defer { freeaddrinfo(addrList) } |
let cursor = addrList!.pointee |
return Data(bytes: cursor.ai_addr, count: Int(cursor.ai_addrlen)) |
} |
extension Process { |
/// The Swift equivalent of `getprogname`. |
static var progName: String { |
return String(CommandLine.arguments[0].characters.split(separator: "/").last!) |
} |
/// An implementation of the `TextOutputStream` protocol that prints to a C `FILE`. |
struct CFileOutputStream : TextOutputStream { |
let file: UnsafeMutablePointer<FILE> |
mutating func write(_ string: String) { |
string.withCString { cStr in |
_ = fputs(cStr, self.file) |
} |
} |
} |
/// The Swift equivalent of `stdout`. |
static var stdout = CFileOutputStream(file: Darwin.stdout) |
/// The Swift equivalent of `stderr`. |
static var stderr = CFileOutputStream(file: Darwin.stderr) |
} |
// MARK: - main function |
/// The main function, called below. |
/// |
/// I created this just to encapsulate the code that is a little complex. |
func main() { |
func usage() -> Never { |
print("usage: \(Process.progName) -h apple.com", to: &Process.stderr) |
print(" \(Process.progName) -a 17.172.224.47", to: &Process.stderr) |
exit(EXIT_FAILURE) |
} |
// Parse any options. |
let m = MainObj() |
var opt: Int32 |
repeat { |
opt = getopt(CommandLine.argc, CommandLine.unsafeArgv, "h:a:") |
switch opt { |
case -1: |
break |
case Int32(UInt8(ascii: "h")): |
let name = String(cString: optarg) |
guard !name.isEmpty else { |
usage() |
} |
m.addressQueries.append( HostAddressQuery(name: name) ) |
case Int32(UInt8(ascii: "a")): |
guard let address = data(for: String(cString: optarg)) else { |
usage() |
} |
m.nameQueries.append( HostNameQuery(address: address) ) |
default: |
usage() |
} |
} while opt != -1 |
// Check for inconsistencies and then run. |
guard m.queryCount != 0 else { |
usage() // nothing to do |
} |
guard optind == CommandLine.argc else { |
usage() // extra stuff an the command line |
} |
m.run() |
} |
main() |
Copyright © 2017 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2017-03-14