mirror of
https://github.com/cirruslabs/tart.git
synced 2026-10-07 14:41:15 +02:00
119 lines
3.3 KiB
Swift
119 lines
3.3 KiB
Swift
import Foundation
|
|
import Network
|
|
|
|
enum LeasesError: Error {
|
|
case UnexpectedFormat(name: String = "unexpected DHCPD leases file format", message: String, line: Int)
|
|
case Truncated(name: String = "truncated DHCPD leases file")
|
|
|
|
var description: String {
|
|
switch self {
|
|
|
|
case .UnexpectedFormat(name: let name, message: let message, line: let line):
|
|
return "\(name) on line \(line): \(message)"
|
|
case .Truncated(name: let name):
|
|
return "\(name)"
|
|
}
|
|
}
|
|
}
|
|
|
|
class Leases {
|
|
private let leases: [MACAddress : Lease]
|
|
|
|
convenience init?() throws {
|
|
try self.init(URL(fileURLWithPath: "/var/db/dhcpd_leases"))
|
|
}
|
|
|
|
convenience init?(_ fromURL: URL) throws {
|
|
do {
|
|
let urlContents = try String(contentsOf: fromURL, encoding: .utf8)
|
|
try self.init(urlContents)
|
|
} catch {
|
|
if error.isFileNotFound() {
|
|
return nil
|
|
}
|
|
|
|
throw error
|
|
}
|
|
}
|
|
|
|
init(_ fromString: String) throws {
|
|
let leases = try Self.retrieveRawLeases(fromString).compactMap({ rawLease in
|
|
Lease(fromRawLease: rawLease)
|
|
}).filter({ lease in
|
|
lease.expiresAt.isInFuture
|
|
}).map({ lease in
|
|
(lease.mac, lease)
|
|
})
|
|
|
|
self.leases = Dictionary(leases) { (left, right) in
|
|
// When duplicate lease is found, prefer a newer lease over the older one
|
|
(left.expiresAt > right.expiresAt) ? left : right
|
|
}
|
|
}
|
|
|
|
/// Parse leases from the host cache similarly to the PLCache_read() function found in Apple's Open Source releases.
|
|
///
|
|
/// [1]: https://github.com/apple-opensource/bootp/blob/master/bootplib/NICache.c#L285-L391
|
|
private static func retrieveRawLeases(_ dhcpdLeasesContents: String) throws -> [[String : String]] {
|
|
var rawLeases: [[String : String]] = Array()
|
|
|
|
enum State {
|
|
case Nowhere
|
|
case Start
|
|
case Body
|
|
case End
|
|
}
|
|
var state = State.Nowhere
|
|
|
|
var currentRawLease: [String : String] = Dictionary()
|
|
|
|
for (lineNumber, line) in dhcpdLeasesContents.split(separator: "\n").enumerated().map({ ($0 + 1, $1) }) {
|
|
if line == "{" {
|
|
// Handle lease block start
|
|
if state != .Nowhere && state != .End {
|
|
throw LeasesError.UnexpectedFormat(message: "unexpected lease block start ({)", line: lineNumber)
|
|
}
|
|
|
|
state = .Start
|
|
} else if line == "}" {
|
|
// Handle lease block end
|
|
if state != .Body {
|
|
throw LeasesError.UnexpectedFormat(message: "unexpected lease block end (})", line: lineNumber)
|
|
}
|
|
|
|
rawLeases.append(currentRawLease)
|
|
currentRawLease = Dictionary()
|
|
|
|
state = .End
|
|
} else {
|
|
// Handle lease block contents
|
|
let lineWithoutTabs = String(line.drop { $0 == " " || $0 == "\t"})
|
|
|
|
if lineWithoutTabs.isEmpty {
|
|
continue
|
|
}
|
|
|
|
let splits = lineWithoutTabs.split(separator: "=", maxSplits: 1)
|
|
if splits.count != 2 {
|
|
throw LeasesError.UnexpectedFormat(message: "key-value pair with only a key", line: lineNumber)
|
|
}
|
|
let (key, value) = (String(splits[0]), String(splits[1]))
|
|
|
|
currentRawLease[key] = value
|
|
|
|
state = .Body
|
|
}
|
|
}
|
|
|
|
if state == .Start || state == .Body {
|
|
throw LeasesError.Truncated()
|
|
}
|
|
|
|
return rawLeases
|
|
}
|
|
|
|
func ResolveMACAddress(macAddress: MACAddress) -> IPv4Address? {
|
|
leases[macAddress]?.ip
|
|
}
|
|
}
|