CommandRouter.swift 12 KB

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