241 lines
8.4 KiB
Swift
241 lines
8.4 KiB
Swift
//
|
|
// NTPExtensions.swift
|
|
// TrueTime
|
|
//
|
|
// Created by Michael Sanders on 7/10/16.
|
|
// Copyright © 2016 Instacart. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
public extension timeval {
|
|
static func uptime() -> timeval {
|
|
let now = timeval.now()
|
|
var boottime = timeval()
|
|
var mib: [CInt] = [CTL_KERN, KERN_BOOTTIME]
|
|
var size = MemoryLayout.stride(ofValue: boottime)
|
|
withFatalErrno { sysctl(&mib, 2, &boottime, &size, nil, 0) }
|
|
return timeval(tv_sec: now.tv_sec - boottime.tv_sec, tv_usec: now.tv_usec - boottime.tv_usec)
|
|
}
|
|
|
|
var milliseconds: Int64 {
|
|
return Int64(tv_sec) * Int64(MSEC_PER_SEC) + Int64(tv_usec) / Int64(USEC_PER_MSEC)
|
|
}
|
|
}
|
|
|
|
extension timeval {
|
|
static func now() -> timeval {
|
|
var tv = timeval()
|
|
withFatalErrno { gettimeofday(&tv, nil) }
|
|
return tv
|
|
}
|
|
}
|
|
|
|
// Represents an amount of time since the NTP epoch, January 1, 1900.
|
|
// https://en.wikipedia.org/wiki/Network_Time_Protocol#Timestamps
|
|
protocol NTPTimeType {
|
|
associatedtype ValueType: UnsignedInteger
|
|
init(whole: ValueType, fraction: ValueType)
|
|
var whole: ValueType { get }
|
|
var fraction: ValueType { get }
|
|
}
|
|
|
|
protocol NTPTimevalConvertible: NTPTimeType {}
|
|
|
|
extension NTPTimeType {
|
|
// Interprets the receiver as an elapsed time in milliseconds.
|
|
var durationInMilliseconds: Int64 {
|
|
return Int64(whole) * Int64(MSEC_PER_SEC) +
|
|
fractionInMicroseconds / Int64(USEC_PER_MSEC)
|
|
}
|
|
|
|
var fractionInMicroseconds: Int64 {
|
|
return Int64(fraction) / Int64(1<<32 / USEC_PER_SEC)
|
|
}
|
|
}
|
|
|
|
extension NTPTimevalConvertible {
|
|
init(timeSince1970 time: timeval) {
|
|
precondition(time.tv_sec >= 0 && time.tv_usec >= 0, "Time must be positive \(time)")
|
|
self.init(whole: ValueType(UInt64(time.tv_sec) + UInt64(secondsFrom1900To1970)),
|
|
fraction: ValueType(UInt64(time.tv_usec) * UInt64(1<<32 / USEC_PER_SEC)))
|
|
}
|
|
|
|
var milliseconds: Int64 {
|
|
return (Int64(whole) - secondsFrom1900To1970) * Int64(MSEC_PER_SEC) +
|
|
fractionInMicroseconds / Int64(USEC_PER_MSEC)
|
|
}
|
|
}
|
|
|
|
extension ntp_time32_t: NTPTimeType {}
|
|
extension ntp_time64_t: NTPTimevalConvertible {}
|
|
|
|
extension TimeInterval {
|
|
init(milliseconds: Int64) {
|
|
self = Double(milliseconds) / Double(MSEC_PER_SEC)
|
|
}
|
|
|
|
init(_ timestamp: timeval) {
|
|
self = Double(timestamp.tv_sec) + Double(timestamp.tv_usec) / Double(USEC_PER_SEC)
|
|
}
|
|
}
|
|
|
|
protocol ByteRepresentable {
|
|
init()
|
|
}
|
|
|
|
extension ByteRepresentable {
|
|
var data: Data {
|
|
var buffer = self
|
|
return Data(bytes: &buffer, count: MemoryLayout.size(ofValue: buffer))
|
|
}
|
|
}
|
|
|
|
extension ntp_packet_t: ByteRepresentable {}
|
|
extension sockaddr_in: ByteRepresentable {}
|
|
extension sockaddr_in6: ByteRepresentable {}
|
|
extension sockaddr_in6: CustomStringConvertible {
|
|
public var description: String {
|
|
var buffer = [Int8](repeating: 0, count: Int(INET6_ADDRSTRLEN))
|
|
var addr = sin6_addr
|
|
inet_ntop(AF_INET6, &addr, &buffer, socklen_t(INET6_ADDRSTRLEN))
|
|
|
|
let host = String(cString: buffer)
|
|
let port = Int(sin6_port)
|
|
return "\(host):\(port)"
|
|
}
|
|
}
|
|
|
|
extension sockaddr_in: CustomStringConvertible {
|
|
public var description: String {
|
|
let host = String(cString: inet_ntoa(sin_addr))
|
|
let port = Int(sin_port)
|
|
return "\(host):\(port)"
|
|
}
|
|
}
|
|
|
|
extension HostResolver: CustomStringConvertible {
|
|
var description: String {
|
|
return "\(type(of: self))(host: \(host), port: \(port) timeout: \(timeout))"
|
|
}
|
|
}
|
|
|
|
extension NTPConnection: CustomStringConvertible {
|
|
var description: String {
|
|
return "\(type(of: self))(socketAddress: \(address), " +
|
|
"timeout: \(timeout), " +
|
|
"maxRetries: \(maxRetries))"
|
|
}
|
|
}
|
|
|
|
extension FrozenNetworkTime: CustomStringConvertible {
|
|
var description: String {
|
|
return "\(type(of: self))(time: \(time), " +
|
|
"uptime: \(uptime.milliseconds) ms, " +
|
|
"serverResponse: \(serverResponse), " +
|
|
"startTime: \(startTime.milliseconds) ms, " +
|
|
"sampleSize: \((sampleSize ?? 0)), " +
|
|
"host: \(host ?? "nil"))"
|
|
}
|
|
}
|
|
|
|
extension NTPResponse: CustomStringConvertible {
|
|
var description: String {
|
|
return "\(type(of: self))(packet: \(packet.description), " +
|
|
"responseTime: \(responseTime) ms, " +
|
|
"receiveTime: \(receiveTime.milliseconds) ms)"
|
|
}
|
|
}
|
|
|
|
extension ntp_packet_t: CustomStringConvertible {
|
|
public var description: String {
|
|
let referenceTime = reference_time.milliseconds
|
|
let originateTime = originate_time.milliseconds
|
|
let receiveTime = receive_time.milliseconds
|
|
let transmitTime = transmit_time.milliseconds
|
|
return "\(type(of: self))(client_mode: \(client_mode.description), " +
|
|
"version_number: \(version_number.description), " +
|
|
"leap_indicator: \(leap_indicator.description), " +
|
|
"stratum: \(stratum.description), " +
|
|
"poll: \(poll.description), " +
|
|
"precision: \(precision.description), " +
|
|
"root_delay: \(root_delay), " +
|
|
"root_dispersion: \(root_dispersion), " +
|
|
"reference_id: \(reference_id), " +
|
|
"reference_time: \(referenceTime) ms, " +
|
|
"originate_time: \(originateTime) ms, " +
|
|
"receive_time: \(receiveTime) ms, " +
|
|
"transmit_time: \(transmitTime) ms)"
|
|
}
|
|
}
|
|
|
|
extension ntp_packet_t {
|
|
var timeDescription: String {
|
|
return "\(type(of: self))(reference_time: + \(reference_time.milliseconds) ms, " +
|
|
"originate_time: \(originate_time.milliseconds) ms, " +
|
|
"receive_time: \(receive_time.milliseconds) ms, " +
|
|
"transmit_time: \(transmit_time.milliseconds) ms)"
|
|
}
|
|
}
|
|
|
|
extension String {
|
|
var localized: String {
|
|
return Bundle.main.localizedString(forKey: self, value: "", table: "TrueTime")
|
|
}
|
|
}
|
|
|
|
extension TrueTimeError: CustomStringConvertible {
|
|
public var description: String {
|
|
switch self {
|
|
case .cannotFindHost: return "The connection failed because the host could not be found.".localized
|
|
case .dnsLookupFailed: return "The connection failed because the DNS lookup failed.".localized
|
|
case .timedOut: return "The connection timed out.".localized
|
|
case .offline: return "The connection failed because the device is not connected to the internet.".localized
|
|
case .badServerResponse: return "The connection received an invalid server response.".localized
|
|
case .noValidPacket: return "No valid NTP packet was found.".localized
|
|
}
|
|
}
|
|
}
|
|
|
|
extension NSError {
|
|
convenience init(errno code: Int32) {
|
|
var userInfo: [String: AnyObject]?
|
|
if let description = String(validatingUTF8: strerror(code)) {
|
|
userInfo = [NSLocalizedDescriptionKey: description as AnyObject]
|
|
}
|
|
self.init(domain: NSPOSIXErrorDomain, code: Int(code), userInfo: userInfo)
|
|
}
|
|
|
|
convenience init(trueTimeError: TrueTimeError) {
|
|
self.init(domain: TrueTimeErrorDomain, code: trueTimeError.rawValue, userInfo: [
|
|
NSLocalizedDescriptionKey: trueTimeError.description
|
|
])
|
|
}
|
|
}
|
|
|
|
func withErrno<X: SignedInteger>(_ block: () -> X) throws -> X {
|
|
let result = block()
|
|
if result < 0 {
|
|
throw NSError(errno: errno)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// Equivalent to `withErrno` but asserts at runtime.
|
|
// Useful when `errno` can only be used to indicate programmer error.
|
|
@discardableResult
|
|
func withFatalErrno<X: SignedInteger>(_ block: () -> X) -> X {
|
|
// swiftlint:disable force_try
|
|
return try! withErrno(block)
|
|
// swiftlint:enable force_try
|
|
}
|
|
|
|
// Number of seconds between Jan 1, 1900 and Jan 1, 1970
|
|
// 70 years plus 17 leap days
|
|
private let secondsFrom1900To1970: Int64 = ((365 * 70) + 17) * 24 * 60 * 60
|
|
|
|
// swiftlint:disable identifier_name
|
|
let MSEC_PER_SEC: UInt64 = 1000
|
|
let USEC_PER_MSEC: UInt64 = 1000
|
|
// swiftlint:enable identifier_name
|