CommandRouter.swift 11 KB

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