CommandRouter.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import Foundation
  2. // ═══════════════════════════════════════════════════════════════════
  3. // ArkOS Command Router
  4. // ═══════════════════════════════════════════════════════════════════
  5. // Routes incoming IPC requests to the appropriate system handler.
  6. // Uses a binary header protocol for efficient, type-safe communication
  7. // without string parsing overhead.
  8. //
  9. // Wire Format (all multi-byte values are big-endian):
  10. // Request: [Command_ID (2B)][Payload_Length (4B)][Payload_Data]
  11. // Response: [Command_ID (2B)][Payload_Length (4B)][Response_Data]
  12. //
  13. // Command Registry:
  14. // 100 — Reserved
  15. // 101 — GET_TIME → Current date/time string
  16. // 102 — GET_IP → Primary IPv4 address
  17. // 103 — GET_BATTERY → Battery percentage and charge state
  18. // 104 — GET_BLUETOOTH → Bluetooth adapter status
  19. // 105 — SHUTDOWN → Initiate system shutdown
  20. // 106 — DUMP_LOGS → Retrieve system log buffer
  21. // 107 — GET_INTERFACES → List all network interfaces with state
  22. // 108 — GET_SERVICES → Service status report
  23. // 109 — RESTART_SERVICE → Restart a named service
  24. // 110 — GET_SYSTEM_INFO → CPU, RAM, uptime summary
  25. // ═══════════════════════════════════════════════════════════════════
  26. // ── Public API Namespaces ─────────────────────────────────────────
  27. /// The `ark` namespace provides the developer-facing API surface
  28. /// for querying OS state. These are high-level wrappers around the
  29. /// raw managers in KernelBridge.swift.
  30. public struct ark {
  31. public struct system {
  32. /// Time and uptime queries.
  33. public struct time {
  34. /// Returns the current date and time as a formatted string.
  35. public static func getCurrentTime() -> String {
  36. let formatter = DateFormatter()
  37. formatter.timeStyle = .medium
  38. formatter.dateStyle = .medium
  39. return formatter.string(from: Date())
  40. }
  41. /// Returns the system uptime in seconds by reading /proc/uptime.
  42. public static func getUptime() -> Double {
  43. if let data = try? String(contentsOfFile: "/proc/uptime") {
  44. let parts = data.split(separator: " ")
  45. if let first = parts.first, let uptimeVal = Double(first) {
  46. return uptimeVal
  47. }
  48. }
  49. return 0.0
  50. }
  51. /// Returns uptime as a human-readable string (e.g. "2h 15m 30s").
  52. public static func getUptimeFormatted() -> String {
  53. let total = Int(getUptime())
  54. let hours = total / 3600
  55. let minutes = (total % 3600) / 60
  56. let seconds = total % 60
  57. if hours > 0 {
  58. return "\(hours)h \(minutes)m \(seconds)s"
  59. } else if minutes > 0 {
  60. return "\(minutes)m \(seconds)s"
  61. }
  62. return "\(seconds)s"
  63. }
  64. }
  65. /// Network state queries.
  66. public struct network {
  67. /// Returns the primary IPv4 address.
  68. public static func getIPAddress() -> String {
  69. return NetworkManager.getInterfaceIP()
  70. }
  71. /// Returns names of all non-loopback interfaces.
  72. public static func getInterfaceNames() -> [String] {
  73. return NetworkManager.getInterfaceNames()
  74. }
  75. /// Returns true if any interface has connectivity.
  76. public static func isConnected() -> Bool {
  77. return NetworkManager.isConnected()
  78. }
  79. /// Returns detailed info for all interfaces.
  80. public static func getAllInterfaces() -> [NetworkManager.InterfaceInfo] {
  81. return NetworkManager.getAllInterfaces()
  82. }
  83. }
  84. /// Raw input queries (for pre-display-server console mode).
  85. public struct input {
  86. public static func readKey() -> Character? {
  87. return InputManager.readKeyStroke()
  88. }
  89. }
  90. }
  91. }
  92. // ── Command ID Constants ──────────────────────────────────────────
  93. /// All supported IPC command identifiers.
  94. public enum CommandID: UInt16 {
  95. case getTime = 101
  96. case getIP = 102
  97. case getBattery = 103
  98. case getBluetooth = 104
  99. case shutdown = 105
  100. case dumpLogs = 106
  101. case getInterfaces = 107
  102. case getServices = 108
  103. case restartService = 109
  104. case getSystemInfo = 110
  105. }
  106. // ── Command Router ────────────────────────────────────────────────
  107. public struct CommandRouter {
  108. /// Routes an incoming binary request to the appropriate handler
  109. /// and returns a binary response frame.
  110. ///
  111. /// - Parameter requestBuffer: Raw bytes of the incoming request.
  112. /// - Returns: Binary response frame with header and payload.
  113. public static func route(requestBuffer: UnsafeRawBufferPointer) -> Data {
  114. // Validate minimum header size (2B command + 4B length = 6B)
  115. guard requestBuffer.count >= 6 else {
  116. LogManager.log("Router: Invalid header (\(requestBuffer.count) bytes, need >= 6)", level: .warn)
  117. return makeResponse(cmdId: 0, payload: "ERROR:INVALID_HEADER")
  118. }
  119. // Parse header fields (zero-copy, big-endian)
  120. let rawCmdId = requestBuffer.load(fromByteOffset: 0, as: UInt16.self).bigEndian
  121. let payloadLen = requestBuffer.load(fromByteOffset: 2, as: UInt32.self).bigEndian
  122. // Validate payload length
  123. guard requestBuffer.count >= 6 + Int(payloadLen) else {
  124. LogManager.log("Router: Payload truncated (declared=\(payloadLen), actual=\(requestBuffer.count - 6))", level: .warn)
  125. return makeResponse(cmdId: rawCmdId, payload: "ERROR:SIZE_MISMATCH")
  126. }
  127. // Extract payload string (zero-copy slice)
  128. let payloadBytes = UnsafeRawBufferPointer(rebasing: requestBuffer[6..<(6 + Int(payloadLen))])
  129. let payloadString = String(decoding: payloadBytes, as: UTF8.self)
  130. LogManager.log("Router: CMD=\(rawCmdId) payload_len=\(payloadLen)")
  131. // Dispatch to the appropriate handler
  132. let responseString: String
  133. if let cmdId = CommandID(rawValue: rawCmdId) {
  134. switch cmdId {
  135. case .getTime:
  136. responseString = ark.system.time.getCurrentTime()
  137. case .getIP:
  138. responseString = ark.system.network.getIPAddress()
  139. case .getBattery:
  140. let pct = PowerManager.getBatteryPercentage()
  141. let state = PowerManager.getChargeState()
  142. let ac = PowerManager.isACConnected() ? "AC" : "Battery"
  143. responseString = "\(pct)%|\(state.rawValue)|\(ac)"
  144. case .getBluetooth:
  145. let enabled = BluetoothManager.isEnabled()
  146. let adapters = BluetoothManager.getAdapters()
  147. responseString = enabled ? "ACTIVE|\(adapters.joined(separator: ","))" : "INACTIVE"
  148. case .shutdown:
  149. responseString = "SHUTTING_DOWN"
  150. // Dispatch shutdown asynchronously so the response is sent first
  151. ResourceController.executeOnCorePool {
  152. sleep(1)
  153. PowerManager.shutdown()
  154. }
  155. case .dumpLogs:
  156. responseString = LogManager.dumpLogs().joined(separator: "\n")
  157. case .getInterfaces:
  158. let interfaces = NetworkManager.getAllInterfaces()
  159. let lines = interfaces.map { iface in
  160. "\(iface.name)|\(iface.isUp ? "UP" : "DOWN")|\(iface.hasCarrier ? "CARRIER" : "NO_CARRIER")|\(iface.ipAddress ?? "none")|\(iface.macAddress ?? "unknown")"
  161. }
  162. responseString = lines.joined(separator: "\n")
  163. case .getServices:
  164. responseString = ServiceManager.shared.getStatusReport()
  165. case .restartService:
  166. if !payloadString.isEmpty {
  167. ServiceManager.shared.stopService(name: payloadString)
  168. ServiceManager.shared.startService(name: payloadString)
  169. responseString = "OK:RESTARTED:\(payloadString)"
  170. } else {
  171. responseString = "ERROR:MISSING_SERVICE_NAME"
  172. }
  173. case .getSystemInfo:
  174. let cores = ResourceController.getCPUCoreCount()
  175. let totalMem = ResourceController.getTotalMemory() / 1024 / 1024
  176. let usedMem = ResourceController.checkMemoryUsage() / 1024 / 1024
  177. let uptime = ark.system.time.getUptimeFormatted()
  178. let connected = NetworkManager.isConnected() ? "Connected" : "Disconnected"
  179. responseString = "cores=\(cores)|ram=\(usedMem)/\(totalMem)MB|uptime=\(uptime)|net=\(connected)"
  180. }
  181. } else {
  182. LogManager.log("Router: Unknown command ID \(rawCmdId)", level: .warn)
  183. responseString = "ERROR:UNKNOWN_COMMAND"
  184. }
  185. return makeResponse(cmdId: rawCmdId, payload: responseString)
  186. }
  187. // ── Response Builder ──────────────────────────────────────────
  188. /// Constructs a binary response frame from a command ID and string payload.
  189. private static func makeResponse(cmdId: UInt16, payload: String) -> Data {
  190. let payloadData = payload.data(using: .utf8) ?? Data()
  191. var response = Data()
  192. response.reserveCapacity(6 + payloadData.count)
  193. var bigEndianCmd = cmdId.bigEndian
  194. var bigEndianLen = UInt32(payloadData.count).bigEndian
  195. withUnsafeBytes(of: &bigEndianCmd) { response.append(contentsOf: $0) }
  196. withUnsafeBytes(of: &bigEndianLen) { response.append(contentsOf: $0) }
  197. response.append(payloadData)
  198. return response
  199. }
  200. }