import Foundation /// The wire schema version. **Must equal** `signal_schema::SCHEMA_VERSION` on the /// Rust side (v5 as of 1.0.0, the 1.0 contract). A reader skips frames whose /// version it does not understand, so this is a hard cross-language contract. public let SCHEMA_VERSION: UInt16 = 5 /// The collector domain. Discriminants **must match** Rust `Source::to_u8`. public enum Source: UInt8 { case terminal = 0 case git = 1 case macos = 2 case hardware = 3 } /// The enum-constrained metric name. Only the variants this collector emits are /// declared here; every discriminant **must match** Rust `SignalName::to_u8`. /// /// The privacy boundary is unchanged for hardware: each of these is an aggregate /// `f64` scalar, and none of them carries a tag (all are emitted with /// `tag == nil`), exactly as `SignalName::allows_tag` is `false` for them. public enum SignalName: UInt8 { case batteryPct = 6 // macos: battery charge percentage [0, 100] case charging = 7 // macos: 1.0 charging, 0.0 not case thermalState = 8 // macos: ProcessInfo thermal state 0..3 case batteryDrawW = 9 // hardware: instantaneous battery draw, watts case cpuLoad = 10 // hardware: aggregate CPU busy fraction [0, 1] /// Human-readable name, matching the Rust metric names (for `--once`). public var label: String { switch self { case .batteryPct: return "battery_pct" case .charging: return "charging" case .thermalState: return "thermal_state" case .batteryDrawW: return "battery_draw_w" case .cpuLoad: return "cpu_load" } } } /// One flat signal record — the Swift mirror of Rust `Signal`. The only payload /// channel is `value: Double` (`f64`); there is deliberately no content field. public struct Signal { public let schemaVersion: UInt16 public let ts: UInt64 public let source: Source public let name: SignalName public let value: Double public let tag: String? public init(ts: UInt64, source: Source, name: SignalName, value: Double, tag: String? = nil) { self.schemaVersion = SCHEMA_VERSION self.ts = ts self.source = source self.name = name self.value = value self.tag = tag } } private extension Array where Element == UInt8 { /// Append a fixed-width integer in little-endian order (matches Rust /// `to_le_bytes`). mutating func appendLE(_ v: T) { var le = v.littleEndian Swift.withUnsafeBytes(of: &le) { append(contentsOf: $0) } } } /// Encode a signal to its length-prefixed wire frame, byte-for-byte identical to /// Rust `signal_schema::wire::encode`. /// /// ```text /// [u32 body_len] /// body = [u16 schema_version][u64 ts][u8 source][u8 name][f64 value] /// [u8 tag_present] ( [u16 tag_len][tag_len bytes utf8] )? /// ``` /// All multi-byte integers and the `f64` (via its IEEE-754 bit pattern) are /// little-endian. public func encode(_ s: Signal) -> [UInt8] { var body: [UInt8] = [] body.appendLE(s.schemaVersion) // u16 body.appendLE(s.ts) // u64 body.append(s.source.rawValue) // u8 body.append(s.name.rawValue) // u8 body.appendLE(s.value.bitPattern) // f64 as its u64 bit pattern, LE if let tag = s.tag { body.append(1) let bytes = Array(tag.utf8) body.appendLE(UInt16(bytes.count)) body.append(contentsOf: bytes) } else { body.append(0) } var frame: [UInt8] = [] frame.appendLE(UInt32(body.count)) frame.append(contentsOf: body) return frame } /// Unix milliseconds, best-effort (matches Rust `now_millis`). public func nowMillis() -> UInt64 { UInt64(Date().timeIntervalSince1970 * 1000.0) }