openc3 7.2.1 → 7.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (502) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/Guardfile +0 -0
  4. data/LICENSE.md +0 -0
  5. data/README.md +0 -0
  6. data/Rakefile +0 -0
  7. data/bin/openc3cli +235 -6
  8. data/bin/pipinstall +5 -4
  9. data/bin/pipuninstall +5 -1
  10. data/bin/uvinstall +156 -0
  11. data/data/config/_array_params.yaml +0 -0
  12. data/data/config/_canvas_values.yaml +0 -0
  13. data/data/config/_graph_params.yaml +0 -0
  14. data/data/config/_id_items.yaml +0 -0
  15. data/data/config/_id_params.yaml +0 -0
  16. data/data/config/_interfaces.yaml +0 -0
  17. data/data/config/_items.yaml +0 -0
  18. data/data/config/_params.yaml +0 -0
  19. data/data/config/command.yaml +0 -0
  20. data/data/config/command_modifiers.yaml +2 -0
  21. data/data/config/command_telemetry.yaml +0 -0
  22. data/data/config/conversions.yaml +0 -0
  23. data/data/config/graph_settings.yaml +0 -0
  24. data/data/config/interface_modifiers.yaml +103 -1
  25. data/data/config/item_modifiers.yaml +2 -1
  26. data/data/config/microservice.yaml +9 -2
  27. data/data/config/param_item_modifiers.yaml +0 -0
  28. data/data/config/parameter_modifiers.yaml +0 -0
  29. data/data/config/plugins.yaml +0 -0
  30. data/data/config/processors.yaml +0 -0
  31. data/data/config/protocols.yaml +0 -0
  32. data/data/config/screen.yaml +2 -1
  33. data/data/config/settings.yaml +0 -0
  34. data/data/config/table_manager.yaml +0 -0
  35. data/data/config/table_parameter_modifiers.yaml +0 -0
  36. data/data/config/target.yaml +21 -0
  37. data/data/config/target_config.yaml +0 -0
  38. data/data/config/telemetry.yaml +0 -0
  39. data/data/config/telemetry_modifiers.yaml +2 -0
  40. data/data/config/tool.yaml +3 -1
  41. data/data/config/unknown.yaml +0 -0
  42. data/data/config/widgets.yaml +22 -3
  43. data/data/xtce_schemas/SpaceSystem_20180204.xsd +5918 -0
  44. data/data/xtce_schemas/xml.xsd +116 -0
  45. data/ext/openc3/ext/array/array.c +0 -0
  46. data/ext/openc3/ext/array/extconf.rb +0 -0
  47. data/ext/openc3/ext/buffered_file/buffered_file.c +0 -0
  48. data/ext/openc3/ext/buffered_file/extconf.rb +0 -0
  49. data/ext/openc3/ext/burst_protocol/burst_protocol.c +6 -0
  50. data/ext/openc3/ext/burst_protocol/extconf.rb +0 -0
  51. data/ext/openc3/ext/config_parser/config_parser.c +0 -0
  52. data/ext/openc3/ext/config_parser/extconf.rb +0 -0
  53. data/ext/openc3/ext/crc/crc.c +0 -0
  54. data/ext/openc3/ext/crc/extconf.rb +0 -0
  55. data/ext/openc3/ext/openc3_io/extconf.rb +0 -0
  56. data/ext/openc3/ext/openc3_io/openc3_io.c +0 -0
  57. data/ext/openc3/ext/packet/extconf.rb +0 -0
  58. data/ext/openc3/ext/packet/packet.c +0 -0
  59. data/ext/openc3/ext/platform/extconf.rb +0 -0
  60. data/ext/openc3/ext/platform/platform.c +4 -1
  61. data/ext/openc3/ext/polynomial_conversion/extconf.rb +0 -0
  62. data/ext/openc3/ext/polynomial_conversion/polynomial_conversion.c +0 -0
  63. data/ext/openc3/ext/string/extconf.rb +0 -0
  64. data/ext/openc3/ext/string/string.c +0 -0
  65. data/ext/openc3/ext/structure/structure.c +49 -17
  66. data/ext/openc3/ext/tabbed_plots_config/extconf.rb +0 -0
  67. data/ext/openc3/ext/tabbed_plots_config/tabbed_plots_config.c +0 -0
  68. data/ext/openc3/ext/telemetry/extconf.rb +0 -0
  69. data/ext/openc3/ext/telemetry/telemetry.c +1 -1
  70. data/lib/cosmos.rb +0 -0
  71. data/lib/cosmosc2.rb +0 -0
  72. data/lib/openc3/accessors/accessor.rb +0 -0
  73. data/lib/openc3/accessors/binary_accessor.rb +63 -9
  74. data/lib/openc3/accessors/cbor_accessor.rb +0 -0
  75. data/lib/openc3/accessors/form_accessor.rb +0 -0
  76. data/lib/openc3/accessors/html_accessor.rb +0 -0
  77. data/lib/openc3/accessors/http_accessor.rb +0 -0
  78. data/lib/openc3/accessors/json_accessor.rb +0 -0
  79. data/lib/openc3/accessors/template_accessor.rb +2 -2
  80. data/lib/openc3/accessors/xml_accessor.rb +0 -0
  81. data/lib/openc3/accessors.rb +0 -0
  82. data/lib/openc3/api/README.md +0 -0
  83. data/lib/openc3/api/api.rb +0 -0
  84. data/lib/openc3/api/authorized_api.rb +0 -0
  85. data/lib/openc3/api/calendar_api.rb +0 -0
  86. data/lib/openc3/api/cmd_api.rb +1 -1
  87. data/lib/openc3/api/config_api.rb +0 -0
  88. data/lib/openc3/api/interface_api.rb +2 -2
  89. data/lib/openc3/api/limits_api.rb +27 -13
  90. data/lib/openc3/api/metrics_api.rb +1 -1
  91. data/lib/openc3/api/offline_access_api.rb +1 -1
  92. data/lib/openc3/api/router_api.rb +3 -3
  93. data/lib/openc3/api/settings_api.rb +22 -18
  94. data/lib/openc3/api/stash_api.rb +1 -1
  95. data/lib/openc3/api/target_api.rb +0 -0
  96. data/lib/openc3/api/tlm_api.rb +5 -4
  97. data/lib/openc3/bridge/bridge.rb +0 -0
  98. data/lib/openc3/bridge/bridge_config.rb +0 -0
  99. data/lib/openc3/bridge/bridge_interface_thread.rb +0 -0
  100. data/lib/openc3/bridge/bridge_router_thread.rb +0 -0
  101. data/lib/openc3/ccsds/ccsds_packet.rb +0 -0
  102. data/lib/openc3/ccsds/ccsds_parser.rb +0 -0
  103. data/lib/openc3/config/config_parser.rb +78 -17
  104. data/lib/openc3/config/meta_config_parser.rb +0 -0
  105. data/lib/openc3/conversions/bit_reverse_conversion.rb +0 -0
  106. data/lib/openc3/conversions/conversion.rb +0 -0
  107. data/lib/openc3/conversions/generic_conversion.rb +0 -0
  108. data/lib/openc3/conversions/ip_read_conversion.rb +0 -0
  109. data/lib/openc3/conversions/ip_write_conversion.rb +0 -0
  110. data/lib/openc3/conversions/object_read_conversion.rb +0 -0
  111. data/lib/openc3/conversions/object_write_conversion.rb +0 -0
  112. data/lib/openc3/conversions/packet_time_formatted_conversion.rb +0 -0
  113. data/lib/openc3/conversions/packet_time_seconds_conversion.rb +0 -0
  114. data/lib/openc3/conversions/polynomial_conversion.rb +0 -0
  115. data/lib/openc3/conversions/processor_conversion.rb +0 -0
  116. data/lib/openc3/conversions/received_count_conversion.rb +0 -0
  117. data/lib/openc3/conversions/received_time_formatted_conversion.rb +0 -0
  118. data/lib/openc3/conversions/received_time_seconds_conversion.rb +0 -0
  119. data/lib/openc3/conversions/segmented_polynomial_conversion.rb +0 -0
  120. data/lib/openc3/conversions/unix_time_conversion.rb +0 -0
  121. data/lib/openc3/conversions/unix_time_formatted_conversion.rb +0 -0
  122. data/lib/openc3/conversions/unix_time_seconds_conversion.rb +0 -0
  123. data/lib/openc3/conversions.rb +0 -0
  124. data/lib/openc3/core_ext/array.rb +0 -0
  125. data/lib/openc3/core_ext/binding.rb +0 -0
  126. data/lib/openc3/core_ext/class.rb +0 -0
  127. data/lib/openc3/core_ext/exception.rb +0 -0
  128. data/lib/openc3/core_ext/faraday.rb +0 -0
  129. data/lib/openc3/core_ext/file.rb +0 -0
  130. data/lib/openc3/core_ext/io.rb +0 -0
  131. data/lib/openc3/core_ext/kernel.rb +0 -0
  132. data/lib/openc3/core_ext/math.rb +0 -0
  133. data/lib/openc3/core_ext/matrix.rb +0 -0
  134. data/lib/openc3/core_ext/objectspace.rb +0 -0
  135. data/lib/openc3/core_ext/openc3_io.rb +0 -0
  136. data/lib/openc3/core_ext/range.rb +0 -0
  137. data/lib/openc3/core_ext/socket.rb +0 -0
  138. data/lib/openc3/core_ext/string.rb +0 -0
  139. data/lib/openc3/core_ext/stringio.rb +0 -0
  140. data/lib/openc3/core_ext/tempfile.rb +0 -0
  141. data/lib/openc3/core_ext/time.rb +0 -0
  142. data/lib/openc3/core_ext.rb +0 -0
  143. data/lib/openc3/ext/.keep +0 -0
  144. data/lib/openc3/interfaces/file_interface.rb +0 -0
  145. data/lib/openc3/interfaces/http_client_interface.rb +1 -1
  146. data/lib/openc3/interfaces/http_server_interface.rb +0 -0
  147. data/lib/openc3/interfaces/interface.rb +5 -4
  148. data/lib/openc3/interfaces/mqtt_interface.rb +0 -0
  149. data/lib/openc3/interfaces/mqtt_stream_interface.rb +2 -0
  150. data/lib/openc3/interfaces/protocols/burst_protocol.rb +27 -0
  151. data/lib/openc3/interfaces/protocols/cmd_response_protocol.rb +0 -0
  152. data/lib/openc3/interfaces/protocols/cobs_protocol.rb +0 -0
  153. data/lib/openc3/interfaces/protocols/crc_protocol.rb +0 -0
  154. data/lib/openc3/interfaces/protocols/fixed_protocol.rb +0 -0
  155. data/lib/openc3/interfaces/protocols/ignore_packet_protocol.rb +0 -0
  156. data/lib/openc3/interfaces/protocols/length_protocol.rb +7 -0
  157. data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +7 -0
  158. data/lib/openc3/interfaces/protocols/protocol.rb +4 -4
  159. data/lib/openc3/interfaces/protocols/slip_protocol.rb +0 -0
  160. data/lib/openc3/interfaces/protocols/template_protocol.rb +2 -2
  161. data/lib/openc3/interfaces/protocols/terminated_protocol.rb +0 -0
  162. data/lib/openc3/interfaces/serial_interface.rb +2 -0
  163. data/lib/openc3/interfaces/simulated_target_interface.rb +0 -0
  164. data/lib/openc3/interfaces/stream_interface.rb +0 -0
  165. data/lib/openc3/interfaces/tcpip_client_interface.rb +2 -0
  166. data/lib/openc3/interfaces/tcpip_server_interface.rb +0 -0
  167. data/lib/openc3/interfaces/udp_interface.rb +16 -5
  168. data/lib/openc3/interfaces.rb +0 -0
  169. data/lib/openc3/io/buffered_file.rb +0 -0
  170. data/lib/openc3/io/io_multiplexer.rb +0 -0
  171. data/lib/openc3/io/json_api.rb +3 -2
  172. data/lib/openc3/io/json_api_object.rb +0 -0
  173. data/lib/openc3/io/json_drb.rb +44 -12
  174. data/lib/openc3/io/json_drb_object.rb +0 -0
  175. data/lib/openc3/io/json_drb_rack.rb +0 -0
  176. data/lib/openc3/io/json_rpc.rb +0 -2
  177. data/lib/openc3/io/posix_serial_driver.rb +0 -0
  178. data/lib/openc3/io/serial_driver.rb +0 -0
  179. data/lib/openc3/io/stderr.rb +0 -0
  180. data/lib/openc3/io/stdout.rb +0 -0
  181. data/lib/openc3/io/udp_sockets.rb +38 -8
  182. data/lib/openc3/io/win32_serial_driver.rb +1 -0
  183. data/lib/openc3/logs/buffered_packet_log_reader.rb +0 -0
  184. data/lib/openc3/logs/buffered_packet_log_writer.rb +0 -0
  185. data/lib/openc3/logs/log_writer.rb +43 -7
  186. data/lib/openc3/logs/packet_log_constants.rb +0 -0
  187. data/lib/openc3/logs/packet_log_reader.rb +23 -2
  188. data/lib/openc3/logs/packet_log_writer.rb +6 -2
  189. data/lib/openc3/logs/stream_log.rb +1 -1
  190. data/lib/openc3/logs/stream_log_pair.rb +0 -0
  191. data/lib/openc3/logs/text_log_writer.rb +0 -0
  192. data/lib/openc3/logs.rb +0 -0
  193. data/lib/openc3/microservices/cleanup_microservice.rb +0 -0
  194. data/lib/openc3/microservices/decom_common.rb +0 -0
  195. data/lib/openc3/microservices/decom_microservice.rb +0 -0
  196. data/lib/openc3/microservices/interface_decom_common.rb +2 -1
  197. data/lib/openc3/microservices/interface_microservice.rb +73 -26
  198. data/lib/openc3/microservices/log_microservice.rb +0 -0
  199. data/lib/openc3/microservices/microservice.rb +3 -3
  200. data/lib/openc3/microservices/multi_microservice.rb +1 -1
  201. data/lib/openc3/microservices/periodic_microservice.rb +2 -2
  202. data/lib/openc3/microservices/plugin_microservice.rb +0 -0
  203. data/lib/openc3/microservices/queue_microservice.rb +0 -0
  204. data/lib/openc3/microservices/router_microservice.rb +0 -0
  205. data/lib/openc3/microservices/scope_cleanup_microservice.rb +0 -0
  206. data/lib/openc3/microservices/text_log_microservice.rb +0 -0
  207. data/lib/openc3/migrations/20220420190000_log_stuff.rb +0 -0
  208. data/lib/openc3/migrations/20221202214600_add_target_names.rb +0 -0
  209. data/lib/openc3/migrations/20221210174900_convert_to_multi.rb +0 -0
  210. data/lib/openc3/migrations/20230915000002_no_scope_log_messages.rb +0 -0
  211. data/lib/openc3/migrations/20231022000000_tlm_viewer_config.rb +0 -0
  212. data/lib/openc3/migrations/20241208080000_no_critical_cmd.rb +0 -0
  213. data/lib/openc3/migrations/20241208080001_no_trigger_group.rb +0 -0
  214. data/lib/openc3/migrations/20250108060000_news_feed.rb +0 -0
  215. data/lib/openc3/migrations/20250402000000_periodic_only_default.rb +0 -0
  216. data/lib/openc3/migrations/20260203000000_remove_store_id.rb +0 -0
  217. data/lib/openc3/migrations/20260204000000_remove_decom_reducer.rb +0 -0
  218. data/lib/openc3/migrations/20260701000000_updated_at_to_int.rb +0 -0
  219. data/lib/openc3/migrations/20260704000000_default_bridge.rb +38 -0
  220. data/lib/openc3/migrations/20260731000000_system_health_json.rb +52 -0
  221. data/lib/openc3/models/activity_model.rb +0 -0
  222. data/lib/openc3/models/auth_model.rb +35 -7
  223. data/lib/openc3/models/bridge_model.rb +133 -0
  224. data/lib/openc3/models/config_keyword_model.rb +63 -0
  225. data/lib/openc3/models/cvt_model.rb +5 -5
  226. data/lib/openc3/models/db_sharded_model.rb +0 -0
  227. data/lib/openc3/models/environment_model.rb +0 -0
  228. data/lib/openc3/models/gem_model.rb +13 -10
  229. data/lib/openc3/models/host_interface_microservice_model.rb +114 -0
  230. data/lib/openc3/models/info_model.rb +0 -0
  231. data/lib/openc3/models/interface_model.rb +179 -20
  232. data/lib/openc3/models/interface_status_model.rb +0 -0
  233. data/lib/openc3/models/metadata_model.rb +0 -0
  234. data/lib/openc3/models/metric_model.rb +0 -0
  235. data/lib/openc3/models/microservice_model.rb +26 -18
  236. data/lib/openc3/models/microservice_status_model.rb +0 -0
  237. data/lib/openc3/models/migration_model.rb +0 -0
  238. data/lib/openc3/models/model.rb +0 -0
  239. data/lib/openc3/models/news_model.rb +0 -0
  240. data/lib/openc3/models/note_model.rb +0 -0
  241. data/lib/openc3/models/offline_access_model.rb +0 -0
  242. data/lib/openc3/models/ping_model.rb +0 -0
  243. data/lib/openc3/models/plugin_model.rb +211 -50
  244. data/lib/openc3/models/plugin_store_model.rb +0 -0
  245. data/lib/openc3/models/process_status_model.rb +0 -0
  246. data/lib/openc3/models/python_package_model.rb +211 -15
  247. data/lib/openc3/models/queue_model.rb +0 -0
  248. data/lib/openc3/models/reaction_model.rb +0 -0
  249. data/lib/openc3/models/reingest_job_model.rb +0 -0
  250. data/lib/openc3/models/router_model.rb +0 -0
  251. data/lib/openc3/models/router_status_model.rb +0 -0
  252. data/lib/openc3/models/scope_model.rb +85 -56
  253. data/lib/openc3/models/script_engine_model.rb +0 -0
  254. data/lib/openc3/models/script_status_model.rb +0 -0
  255. data/lib/openc3/models/secret_model.rb +0 -0
  256. data/lib/openc3/models/setting_model.rb +546 -3
  257. data/lib/openc3/models/sorted_model.rb +0 -0
  258. data/lib/openc3/models/stash_model.rb +0 -0
  259. data/lib/openc3/models/target_model.rb +210 -31
  260. data/lib/openc3/models/timeline_model.rb +0 -0
  261. data/lib/openc3/models/tool_config_model.rb +0 -0
  262. data/lib/openc3/models/tool_model.rb +34 -20
  263. data/lib/openc3/models/trigger_group_model.rb +0 -0
  264. data/lib/openc3/models/trigger_model.rb +20 -0
  265. data/lib/openc3/models/widget_model.rb +0 -0
  266. data/lib/openc3/operators/microservice_operator.rb +79 -8
  267. data/lib/openc3/operators/operator.rb +4 -4
  268. data/lib/openc3/packets/binary_accessor.rb +0 -0
  269. data/lib/openc3/packets/command_validator.rb +0 -0
  270. data/lib/openc3/packets/commands.rb +4 -4
  271. data/lib/openc3/packets/json_packet.rb +0 -0
  272. data/lib/openc3/packets/limits.rb +15 -7
  273. data/lib/openc3/packets/limits_response.rb +0 -0
  274. data/lib/openc3/packets/packet.rb +5 -1
  275. data/lib/openc3/packets/packet_config.rb +0 -0
  276. data/lib/openc3/packets/packet_item.rb +0 -0
  277. data/lib/openc3/packets/packet_item_limits.rb +0 -0
  278. data/lib/openc3/packets/parsers/format_string_parser.rb +0 -0
  279. data/lib/openc3/packets/parsers/limits_parser.rb +0 -0
  280. data/lib/openc3/packets/parsers/limits_response_parser.rb +0 -0
  281. data/lib/openc3/packets/parsers/packet_item_parser.rb +0 -0
  282. data/lib/openc3/packets/parsers/packet_parser.rb +0 -0
  283. data/lib/openc3/packets/parsers/processor_parser.rb +0 -0
  284. data/lib/openc3/packets/parsers/state_parser.rb +1 -1
  285. data/lib/openc3/packets/parsers/xtce_converter.rb +183 -103
  286. data/lib/openc3/packets/parsers/xtce_parser.rb +71 -23
  287. data/lib/openc3/packets/structure.rb +9 -3
  288. data/lib/openc3/packets/structure_item.rb +0 -0
  289. data/lib/openc3/packets/telemetry.rb +3 -3
  290. data/lib/openc3/processors/processor.rb +2 -0
  291. data/lib/openc3/processors/statistics_processor.rb +0 -0
  292. data/lib/openc3/processors/watermark_processor.rb +0 -0
  293. data/lib/openc3/processors.rb +0 -0
  294. data/lib/openc3/script/api_shared.rb +0 -0
  295. data/lib/openc3/script/autonomic.rb +12 -12
  296. data/lib/openc3/script/calendar.rb +0 -0
  297. data/lib/openc3/script/commands.rb +1 -1
  298. data/lib/openc3/script/critical_cmd.rb +0 -0
  299. data/lib/openc3/script/exceptions.rb +0 -0
  300. data/lib/openc3/script/extract.rb +20 -25
  301. data/lib/openc3/script/limits.rb +0 -0
  302. data/lib/openc3/script/metadata.rb +0 -0
  303. data/lib/openc3/script/packages.rb +0 -0
  304. data/lib/openc3/script/plugins.rb +0 -0
  305. data/lib/openc3/script/queue.rb +0 -0
  306. data/lib/openc3/script/screen.rb +0 -0
  307. data/lib/openc3/script/script.rb +2 -2
  308. data/lib/openc3/script/script_runner.rb +0 -0
  309. data/lib/openc3/script/storage.rb +4 -4
  310. data/lib/openc3/script/suite.rb +0 -0
  311. data/lib/openc3/script/suite_results.rb +0 -0
  312. data/lib/openc3/script/suite_runner.rb +26 -0
  313. data/lib/openc3/script/tables.rb +0 -0
  314. data/lib/openc3/script/telemetry.rb +9 -3
  315. data/lib/openc3/script/web_socket_api.rb +247 -134
  316. data/lib/openc3/script.rb +0 -0
  317. data/lib/openc3/script_engines/script_engine.rb +0 -0
  318. data/lib/openc3/streams/mqtt_stream.rb +0 -0
  319. data/lib/openc3/streams/serial_stream.rb +0 -0
  320. data/lib/openc3/streams/stream.rb +0 -0
  321. data/lib/openc3/streams/tcpip_client_stream.rb +0 -0
  322. data/lib/openc3/streams/tcpip_socket_stream.rb +0 -0
  323. data/lib/openc3/streams/web_socket_client_stream.rb +0 -0
  324. data/lib/openc3/subpacketizers/subpacketizer.rb +0 -0
  325. data/lib/openc3/system/system.rb +0 -0
  326. data/lib/openc3/system/target.rb +0 -0
  327. data/lib/openc3/system.rb +0 -0
  328. data/lib/openc3/tools/cmd_tlm_server/api.rb +0 -0
  329. data/lib/openc3/tools/cmd_tlm_server/interface_thread.rb +7 -0
  330. data/lib/openc3/tools/table_manager/table.rb +0 -0
  331. data/lib/openc3/tools/table_manager/table_config.rb +0 -0
  332. data/lib/openc3/tools/table_manager/table_item.rb +0 -0
  333. data/lib/openc3/tools/table_manager/table_item_parser.rb +0 -0
  334. data/lib/openc3/tools/table_manager/table_manager_core.rb +0 -0
  335. data/lib/openc3/tools/table_manager/table_parser.rb +0 -0
  336. data/lib/openc3/tools/test_runner/test.rb +0 -0
  337. data/lib/openc3/top_level.rb +1 -1
  338. data/lib/openc3/topics/autonomic_topic.rb +0 -0
  339. data/lib/openc3/topics/calendar_topic.rb +0 -0
  340. data/lib/openc3/topics/command_decom_topic.rb +16 -1
  341. data/lib/openc3/topics/command_topic.rb +0 -0
  342. data/lib/openc3/topics/config_topic.rb +0 -0
  343. data/lib/openc3/topics/decom_interface_topic.rb +2 -1
  344. data/lib/openc3/topics/interface_topic.rb +8 -2
  345. data/lib/openc3/topics/limits_event_topic.rb +1 -1
  346. data/lib/openc3/topics/notebook_topic.rb +0 -0
  347. data/lib/openc3/topics/queue_topic.rb +0 -0
  348. data/lib/openc3/topics/router_topic.rb +6 -1
  349. data/lib/openc3/topics/system_events_topic.rb +0 -0
  350. data/lib/openc3/topics/telemetry_decom_topic.rb +0 -0
  351. data/lib/openc3/topics/telemetry_topic.rb +0 -0
  352. data/lib/openc3/topics/timeline_topic.rb +0 -0
  353. data/lib/openc3/topics/topic.rb +0 -0
  354. data/lib/openc3/utilities/authentication.rb +4 -4
  355. data/lib/openc3/utilities/authorization.rb +0 -0
  356. data/lib/openc3/utilities/aws_bucket.rb +12 -4
  357. data/lib/openc3/utilities/bucket.rb +1 -1
  358. data/lib/openc3/utilities/bucket_file_cache.rb +55 -22
  359. data/lib/openc3/utilities/bucket_require.rb +0 -0
  360. data/lib/openc3/utilities/bucket_utilities.rb +3 -4
  361. data/lib/openc3/utilities/cli_generator.rb +0 -0
  362. data/lib/openc3/utilities/cmd_log.rb +0 -0
  363. data/lib/openc3/utilities/config_overlay.rb +75 -0
  364. data/lib/openc3/utilities/cosmos_rails_formatter.rb +0 -0
  365. data/lib/openc3/utilities/crc.rb +59 -32
  366. data/lib/openc3/utilities/csv.rb +5 -5
  367. data/lib/openc3/utilities/ctrf.rb +0 -0
  368. data/lib/openc3/utilities/env_helper.rb +0 -0
  369. data/lib/openc3/utilities/local_bucket.rb +0 -0
  370. data/lib/openc3/utilities/local_mode.rb +36 -7
  371. data/lib/openc3/utilities/logger.rb +30 -8
  372. data/lib/openc3/utilities/message_log.rb +0 -0
  373. data/lib/openc3/utilities/metric.rb +0 -0
  374. data/lib/openc3/utilities/migration.rb +0 -0
  375. data/lib/openc3/utilities/open_telemetry.rb +0 -0
  376. data/lib/openc3/utilities/process_manager.rb +0 -0
  377. data/lib/openc3/utilities/pypi_url.rb +0 -0
  378. data/lib/openc3/utilities/python_proxy.rb +0 -0
  379. data/lib/openc3/utilities/python_venv.rb +135 -0
  380. data/lib/openc3/utilities/quaternion.rb +0 -0
  381. data/lib/openc3/utilities/questdb_client.rb +0 -0
  382. data/lib/openc3/utilities/redis_secrets.rb +0 -0
  383. data/lib/openc3/utilities/reingest_job.rb +0 -0
  384. data/lib/openc3/utilities/ruby_lex_utils.rb +5 -1
  385. data/lib/openc3/utilities/rubygems_url.rb +39 -0
  386. data/lib/openc3/utilities/running_script.rb +119 -95
  387. data/lib/openc3/utilities/s3_autoload.rb +5 -5
  388. data/lib/openc3/utilities/script.rb +52 -12
  389. data/lib/openc3/utilities/secrets.rb +48 -3
  390. data/lib/openc3/utilities/simulated_target.rb +0 -0
  391. data/lib/openc3/utilities/sleeper.rb +0 -0
  392. data/lib/openc3/utilities/store.rb +0 -0
  393. data/lib/openc3/utilities/store_autoload.rb +2 -2
  394. data/lib/openc3/utilities/store_queued.rb +0 -0
  395. data/lib/openc3/utilities/target_file.rb +18 -1
  396. data/lib/openc3/utilities/thread_manager.rb +0 -0
  397. data/lib/openc3/utilities/throttle.rb +0 -0
  398. data/lib/openc3/utilities/zip.rb +0 -0
  399. data/lib/openc3/utilities.rb +0 -0
  400. data/lib/openc3/version.rb +6 -6
  401. data/lib/openc3/win32/excel.rb +0 -0
  402. data/lib/openc3/win32/win32.rb +0 -0
  403. data/lib/openc3/win32/win32_main.rb +0 -0
  404. data/lib/openc3.rb +2 -2
  405. data/tasks/gemfile_stats.rake +0 -0
  406. data/tasks/spec.rake +0 -0
  407. data/templates/command_validator/command_validator.py +0 -0
  408. data/templates/command_validator/command_validator.rb +0 -0
  409. data/templates/conversion/conversion.py +0 -0
  410. data/templates/conversion/conversion.rb +0 -0
  411. data/templates/limits_response/response.py +0 -0
  412. data/templates/limits_response/response.rb +0 -0
  413. data/templates/microservice/microservices/TEMPLATE/microservice.py +0 -0
  414. data/templates/microservice/microservices/TEMPLATE/microservice.rb +0 -0
  415. data/templates/plugin/.gitignore +0 -0
  416. data/templates/plugin/LICENSE.md +0 -0
  417. data/templates/plugin/README.md +0 -0
  418. data/templates/plugin/Rakefile +0 -0
  419. data/templates/plugin/plugin.gemspec +1 -1
  420. data/templates/plugin/plugin.txt +0 -0
  421. data/templates/processor/processor.py +1 -1
  422. data/templates/processor/processor.rb +0 -0
  423. data/templates/target/targets/TARGET/cmd_tlm/cmd.txt +0 -0
  424. data/templates/target/targets/TARGET/cmd_tlm/tlm.txt +0 -0
  425. data/templates/target/targets/TARGET/lib/target.py +0 -0
  426. data/templates/target/targets/TARGET/lib/target.rb +0 -0
  427. data/templates/target/targets/TARGET/procedures/procedure.py +0 -0
  428. data/templates/target/targets/TARGET/procedures/procedure.rb +0 -0
  429. data/templates/target/targets/TARGET/public/README.txt +0 -0
  430. data/templates/target/targets/TARGET/screens/status.txt +0 -0
  431. data/templates/target/targets/TARGET/target.txt +0 -0
  432. data/templates/tool_angular/.editorconfig +0 -0
  433. data/templates/tool_angular/.gitignore +0 -0
  434. data/templates/tool_angular/.vscode/extensions.json +0 -0
  435. data/templates/tool_angular/.vscode/launch.json +0 -0
  436. data/templates/tool_angular/.vscode/tasks.json +0 -0
  437. data/templates/tool_angular/angular.json +0 -0
  438. data/templates/tool_angular/extra-webpack.config.js +0 -0
  439. data/templates/tool_angular/package.json +2 -2
  440. data/templates/tool_angular/src/app/app-routing.module.ts +0 -0
  441. data/templates/tool_angular/src/app/app.component.html +0 -0
  442. data/templates/tool_angular/src/app/app.component.scss +0 -0
  443. data/templates/tool_angular/src/app/app.component.spec.ts +0 -0
  444. data/templates/tool_angular/src/app/app.component.ts +0 -0
  445. data/templates/tool_angular/src/app/app.module.ts +0 -0
  446. data/templates/tool_angular/src/app/custom-overlay-container.ts +0 -0
  447. data/templates/tool_angular/src/app/empty-route/empty-route.component.ts +0 -0
  448. data/templates/tool_angular/src/app/openc3-api.d.ts +0 -0
  449. data/templates/tool_angular/src/assets/.gitkeep +0 -0
  450. data/templates/tool_angular/src/environments/environment.prod.ts +0 -0
  451. data/templates/tool_angular/src/environments/environment.ts +0 -0
  452. data/templates/tool_angular/src/favicon.ico +0 -0
  453. data/templates/tool_angular/src/index.html +0 -0
  454. data/templates/tool_angular/src/main.single-spa.ts +0 -0
  455. data/templates/tool_angular/src/single-spa/asset-url.ts +0 -0
  456. data/templates/tool_angular/src/single-spa/single-spa-props.ts +0 -0
  457. data/templates/tool_angular/src/styles.scss +0 -0
  458. data/templates/tool_angular/tsconfig.app.json +0 -0
  459. data/templates/tool_angular/tsconfig.json +0 -0
  460. data/templates/tool_angular/tsconfig.spec.json +0 -0
  461. data/templates/tool_react/.eslintrc +0 -0
  462. data/templates/tool_react/.gitignore +0 -0
  463. data/templates/tool_react/.prettierignore +0 -0
  464. data/templates/tool_react/babel.config.json +0 -0
  465. data/templates/tool_react/jest.config.js +0 -0
  466. data/templates/tool_react/package.json +1 -1
  467. data/templates/tool_react/src/openc3-tool_name.js +0 -0
  468. data/templates/tool_react/src/root.component.js +0 -0
  469. data/templates/tool_react/src/root.component.test.js +0 -0
  470. data/templates/tool_react/webpack.config.js +0 -0
  471. data/templates/tool_svelte/.gitignore +0 -0
  472. data/templates/tool_svelte/.prettierignore +0 -0
  473. data/templates/tool_svelte/.prettierrc.js +0 -0
  474. data/templates/tool_svelte/babel.config.js +0 -0
  475. data/templates/tool_svelte/build/smui.css +0 -0
  476. data/templates/tool_svelte/jest.config.js +0 -0
  477. data/templates/tool_svelte/package.json +1 -1
  478. data/templates/tool_svelte/rollup.config.js +0 -0
  479. data/templates/tool_svelte/src/App.svelte +0 -0
  480. data/templates/tool_svelte/src/App.test.js +0 -0
  481. data/templates/tool_svelte/src/theme/_smui-theme.scss +0 -0
  482. data/templates/tool_svelte/src/tool_name.js +0 -0
  483. data/templates/tool_vue/.env.standalone +0 -0
  484. data/templates/tool_vue/.gitignore +0 -0
  485. data/templates/tool_vue/.prettierrc.cjs +0 -0
  486. data/templates/tool_vue/eslint.config.mjs +0 -0
  487. data/templates/tool_vue/jsconfig.json +0 -0
  488. data/templates/tool_vue/package.json +3 -3
  489. data/templates/tool_vue/src/App.vue +0 -0
  490. data/templates/tool_vue/src/main.js +0 -0
  491. data/templates/tool_vue/src/router.js +0 -0
  492. data/templates/tool_vue/src/tools/tool_name/tool_name.vue +0 -0
  493. data/templates/tool_vue/vite.config.js +1 -1
  494. data/templates/widget/.prettierrc.cjs +0 -0
  495. data/templates/widget/LICENSE.md +0 -0
  496. data/templates/widget/Rakefile +0 -0
  497. data/templates/widget/package.json +2 -2
  498. data/templates/widget/src/Widget.vue +0 -0
  499. data/templates/widget/vite.config.js +0 -0
  500. metadata +40 -11
  501. data/templates/tool_vue/.nycrc +0 -3
  502. data/templates/widget/.nycrc +0 -3
@@ -195,7 +195,7 @@ module OpenC3
195
195
  msg_hash = critical_model.cmd_hash
196
196
  release_critical = true
197
197
  else
198
- next "Critical command #{msg_hash['release_critical']} not found"
198
+ next "Critical command '#{msg_hash['release_critical']}' not found (interface command processing)"
199
199
  end
200
200
  end
201
201
  if msg_hash.key?('target_control')
@@ -221,7 +221,12 @@ module OpenC3
221
221
  next 'SUCCESS'
222
222
  end
223
223
  if msg_hash.key?('interface_details')
224
- next @interface.details.as_json.to_json(allow_nan: true)
224
+ begin
225
+ next @interface.details.as_json.to_json(allow_nan: true)
226
+ rescue => e
227
+ @logger.error "#{@interface.name}: interface_details: #{e.formatted}"
228
+ next e.message
229
+ end
225
230
  end
226
231
  end
227
232
 
@@ -508,7 +513,12 @@ module OpenC3
508
513
  next 'SUCCESS'
509
514
  end
510
515
  if msg_hash.key?('router_details')
511
- next @router.details.as_json.to_json(allow_nan: true)
516
+ begin
517
+ next @router.details.as_json.to_json(allow_nan: true)
518
+ rescue => e
519
+ @logger.error "#{@router.name}: router_details: #{e.formatted}"
520
+ next e.message
521
+ end
512
522
  end
513
523
  next 'SUCCESS'
514
524
  end
@@ -610,7 +620,26 @@ module OpenC3
610
620
  # Called to connect the interface/router. It takes optional parameters to
611
621
  # rebuilt the interface/router. Once we set the state to 'ATTEMPTING' the
612
622
  # run method handles the actual connection.
623
+ # Connecting an interface/router which is already CONNECTED is a no-op.
624
+ # Without this the existing (working) connection would be torn down and
625
+ # rebuilt which can take up to the read_timeout to detect. Callers who want
626
+ # to force a reconnect should disconnect first or pass new parameters.
613
627
  def attempting(*params)
628
+ if params.empty? and @interface.state == 'CONNECTED' and @interface.connected?
629
+ @logger.info "#{@interface.name}: Connect ignored, already connected"
630
+ return @interface
631
+ end
632
+
633
+ attempt_connection(*params)
634
+ end
635
+
636
+ # Sets the state to 'ATTEMPTING', first rebuilding the interface/router if
637
+ # parameters are given, so the run method performs the actual connection.
638
+ # Unlike attempting() this always transitions. The reconnect path in
639
+ # disconnect() requires that, since @interface.disconnect may have raised or
640
+ # left connected? true, which would make attempting() ignore the request and
641
+ # leave the interface stuck in 'CONNECTED' with no way back to a connection.
642
+ def attempt_connection(*params)
614
643
  unless params.empty?
615
644
  @interface.disconnect()
616
645
  # Build New Interface, this can fail if passed bad parameters
@@ -828,6 +857,13 @@ module OpenC3
828
857
 
829
858
  def connect
830
859
  @logger.info "#{@interface.name}: Connect #{@interface.connection_string}"
860
+ # Interface connect implementations typically overwrite their stream / socket
861
+ # so cleanly close any existing connection rather than leaking it
862
+ begin
863
+ @interface.disconnect if @interface.connected?
864
+ rescue => e
865
+ @logger.error "Disconnect: #{@interface.name}: #{e.formatted}"
866
+ end
831
867
  begin
832
868
  @interface.connect
833
869
  @interface.post_connect
@@ -849,38 +885,49 @@ module OpenC3
849
885
  end
850
886
 
851
887
  def disconnect(allow_reconnect = true)
852
- return if @interface.state == 'DISCONNECTED' && !@interface.connected?
853
-
854
- # Synchronize the calls to @interface.disconnect since it takes an unknown
855
- # amount of time. If two calls to disconnect stack up, the if statement
856
- # should avoid multiple calls to disconnect.
888
+ reconnect = false
889
+
890
+ # Two threads reach here for a single connection loss: the cmd handler
891
+ # thread servicing a disconnect directive, and the run thread coming back
892
+ # out of read (or out of the connection maintenance sleep). The redundant
893
+ # check below and the state change that records the disconnect must be in
894
+ # the same critical section, otherwise the second thread reads the state
895
+ # before the first has updated it and disconnects the interface twice.
857
896
  @mutex.synchronize do
897
+ # A disconnect has already been performed so there is nothing left to do
898
+ return if @interface.state == 'DISCONNECTED' && !@interface.connected?
899
+
900
+ # Call disconnect without consulting connected? so any resources the
901
+ # interface is still holding are cleaned up. It takes an unknown amount
902
+ # of time which is the other reason for the mutex.
858
903
  begin
859
- @interface.disconnect if @interface.connected?
904
+ @interface.disconnect
860
905
  rescue => e
861
906
  @logger.error "Disconnect: #{@interface.name}: #{e.formatted}"
862
907
  end
863
- end
864
908
 
865
- # If the interface is set to auto_reconnect then delay so the thread
866
- # can come back around and allow the interface a chance to reconnect.
867
- # Skip reconnect if stop() has been called to avoid re-creating the status model
868
- if allow_reconnect and @interface.auto_reconnect and @interface.state != 'DISCONNECTED' and !@cancel_thread
869
- attempting()
870
- if !@cancel_thread
871
- # @logger.debug "reconnect delay: #{@interface.reconnect_delay}"
872
- @interface_thread_sleeper.sleep(@interface.reconnect_delay)
873
- end
874
- else
875
- @interface.state = 'DISCONNECTED'
876
- unless @cancel_thread
877
- if @interface_or_router == 'INTERFACE'
878
- InterfaceStatusModel.set(@interface.as_json(), queued: true, scope: @scope)
879
- else
880
- RouterStatusModel.set(@interface.as_json(), queued: true, scope: @scope)
909
+ # If the interface is set to auto_reconnect then delay so the thread
910
+ # can come back around and allow the interface a chance to reconnect.
911
+ # Skip reconnect if stop() has been called to avoid re-creating the status model
912
+ reconnect = allow_reconnect && @interface.auto_reconnect && @interface.state != 'DISCONNECTED' && !@cancel_thread
913
+ if reconnect
914
+ attempt_connection()
915
+ else
916
+ @interface.state = 'DISCONNECTED'
917
+ unless @cancel_thread
918
+ if @interface_or_router == 'INTERFACE'
919
+ InterfaceStatusModel.set(@interface.as_json(), queued: true, scope: @scope)
920
+ else
921
+ RouterStatusModel.set(@interface.as_json(), queued: true, scope: @scope)
922
+ end
881
923
  end
882
924
  end
883
925
  end
926
+
927
+ # Sleep outside the mutex so stop() and connect() are not blocked for the
928
+ # whole reconnect delay
929
+ # @logger.debug "reconnect delay: #{@interface.reconnect_delay}"
930
+ @interface_thread_sleeper.sleep(@interface.reconnect_delay) if reconnect && !@cancel_thread
884
931
  end
885
932
 
886
933
  # Disconnect from the interface and stop the thread
File without changes
@@ -43,7 +43,7 @@ module OpenC3
43
43
  attr_accessor :secrets
44
44
 
45
45
  def self.run(name = nil)
46
- name = ENV['OPENC3_MICROSERVICE_NAME'] unless name
46
+ name = ENV.fetch('OPENC3_MICROSERVICE_NAME', nil) unless name
47
47
  microservice = self.new(name)
48
48
  thread = Thread.new do
49
49
  begin
@@ -150,7 +150,7 @@ module OpenC3
150
150
  cmd_array = @config["cmd"]
151
151
 
152
152
  # Get Microservice files from bucket storage
153
- bucket = ENV['OPENC3_CONFIG_BUCKET']
153
+ bucket = ENV.fetch('OPENC3_CONFIG_BUCKET') { raise 'OPENC3_CONFIG_BUCKET environment variable is required' }
154
154
  client = Bucket.getClient()
155
155
 
156
156
  prefix = "#{@scope}/microservices/#{@name}/"
@@ -160,7 +160,7 @@ module OpenC3
160
160
  # unreachable while many microservices start at once. Rather than crash
161
161
  # and CrashLoopBackOff (which can outlast deploy timeouts), retry for a
162
162
  # bounded time before giving up.
163
- startup_timeout = (ENV['OPENC3_MICROSERVICE_STARTUP_BUCKET_TIMEOUT'] || 60).to_f
163
+ startup_timeout = (ENV.fetch('OPENC3_MICROSERVICE_STARTUP_BUCKET_TIMEOUT', 60)).to_f
164
164
  startup_deadline = Time.now + startup_timeout
165
165
  begin
166
166
  file_count = 0
@@ -1,6 +1,6 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
- # Copyright 2024 OpenC3 Inc.
3
+ # Copyright 2026 OpenC3, Inc.
4
4
  # All Rights Reserved.
5
5
  #
6
6
  # This program is distributed in the hope that it will be useful,
@@ -46,8 +46,8 @@ module OpenC3
46
46
  NewsModel.news_error("Error contacting OpenC3 news feed (status: #{response.status})")
47
47
  end
48
48
  end
49
- rescue Exception => e
50
- NewsModel.news_error("Error contacting OpenC3 news feed. #{e.message})")
49
+ rescue StandardError => e
50
+ NewsModel.news_error("Error contacting OpenC3 news feed. #{e.message}")
51
51
  end
52
52
 
53
53
  def run
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,38 @@
1
+ # Copyright 2026 OpenC3, Inc.
2
+ # All Rights Reserved.
3
+ #
4
+ # This program is distributed in the hope that it will be useful,
5
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
6
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7
+ # See LICENSE.md for more details.
8
+
9
+ # This file may also be used under the terms of a commercial license
10
+ # if purchased from OpenC3, Inc.
11
+
12
+ require 'openc3/utilities/migration'
13
+ require 'openc3/models/scope_model'
14
+ require 'openc3/models/microservice_model'
15
+ require 'openc3/models/bridge_model'
16
+
17
+ module OpenC3
18
+ # Ensure every existing scope has a DEFAULT bridge_microservice (the Iroh hub
19
+ # that lets openc3-app run COSMOS interfaces on the host). New scopes get one
20
+ # via ScopeModel#deploy; this backfills any scope created before that.
21
+ class DefaultBridge < Migration
22
+ def self.run
23
+ ScopeModel.get_all_models(scope: nil).each do |scope, scope_model|
24
+ name = "#{scope}__BRIDGE__DEFAULT"
25
+ next if MicroserviceModel.get_model(name: name, scope: scope)
26
+
27
+ microservice = BridgeModel.build_microservice(bridge_name: "DEFAULT", scope: scope, shard: scope_model.shard)
28
+ microservice.create
29
+ microservice.deploy
30
+ Logger.info("Added DEFAULT bridge_microservice to scope #{scope}")
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ unless ENV['OPENC3_NO_MIGRATE']
37
+ OpenC3::DefaultBridge.run
38
+ end
@@ -0,0 +1,52 @@
1
+ # Copyright 2026 OpenC3, Inc.
2
+ # All Rights Reserved.
3
+ #
4
+ # This program is distributed in the hope that it will be useful,
5
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
6
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7
+ # See LICENSE.md for more details.
8
+ #
9
+ # This file may also be used under the terms of a commercial license
10
+ # if purchased from OpenC3, Inc.
11
+
12
+ require 'json'
13
+ require 'openc3/utilities/local_mode'
14
+ require 'openc3/utilities/migration'
15
+ require 'openc3/models/scope_model'
16
+ require 'openc3/models/setting_model'
17
+
18
+ module OpenC3
19
+ # The system_health setting was originally stored as a Ruby Hash. Local mode
20
+ # writes settings to disk with File.write and reads them back as a String, so
21
+ # the Hash round tripped into a Ruby inspect String which nothing can parse.
22
+ # Store it as a JSON string like every other setting and repair existing data.
23
+ class SystemHealthJson < Migration
24
+ def self.run
25
+ setting = SettingModel.get(name: 'system_health')
26
+ data = repair(setting && setting['data'])
27
+ SettingModel.set({ name: 'system_health', data: JSON.generate(data) }, scope: 'DEFAULT')
28
+ # Repair the local mode file as well or sync_settings puts the bad data back
29
+ LocalMode.save_setting('DEFAULT', 'system_health', JSON.generate(data))
30
+ end
31
+
32
+ # @return [Hash] the existing settings if they can be recovered, otherwise defaults
33
+ def self.repair(data)
34
+ return ScopeModel::SYSTEM_HEALTH_DEFAULTS if data.nil?
35
+ return data if Hash === data
36
+ return ScopeModel::SYSTEM_HEALTH_DEFAULTS unless String === data
37
+ begin
38
+ parsed = JSON.parse(data)
39
+ rescue JSON::ParserError
40
+ # Ruby inspect format, e.g. {"cpu" => {"redThreshold" => 90, ... => nil}}
41
+ # Ruby 3.3 and earlier had no spaces around the rocket. All the values are
42
+ # numbers, booleans, or nil so there's nothing to accidentally rewrite.
43
+ parsed = JSON.parse(data.gsub(/\s*=>\s*/, ': ').gsub(/\bnil\b/, 'null')) rescue nil
44
+ end
45
+ Hash === parsed ? parsed : ScopeModel::SYSTEM_HEALTH_DEFAULTS
46
+ end
47
+ end
48
+ end
49
+
50
+ unless ENV['OPENC3_NO_MIGRATE']
51
+ OpenC3::SystemHealthJson.run
52
+ end
File without changes
@@ -27,7 +27,7 @@ module OpenC3
27
27
  PRIMARY_KEY = 'OPENC3__TOKEN' # for argon2 password hash
28
28
  SESSIONS_KEY = 'OPENC3__SESSIONS' # for hash containing session tokens
29
29
 
30
- # The length of time in minutes to keep redis values in memory
30
+ # The length of time in seconds to keep redis values in memory
31
31
  PW_HASH_CACHE_TIMEOUT = 5
32
32
  SESSION_CACHE_TIMEOUT = 5
33
33
 
@@ -44,6 +44,21 @@ module OpenC3
44
44
  SESSION_PREFIX = "ses_"
45
45
  OTP_PREFIX = "otp_"
46
46
 
47
+ # A session token is SESSION_PREFIX followed by the unpadded urlsafe base64
48
+ # encoding of 16 random bytes, i.e. exactly 22 characters of [A-Za-z0-9_-].
49
+ # Keep this in sync with generate_session.
50
+ SESSION_TOKEN_REGEX = /\A#{SESSION_PREFIX}[A-Za-z0-9_-]{22}\z/
51
+
52
+ # Whether the given value could have been produced by generate_session.
53
+ # Callers handling a token from an unauthenticated request use this to
54
+ # reject garbage before doing any Redis work. Checking only the prefix is
55
+ # not enough: something like "ses_AAA" would pass and still cost us a read.
56
+ # @param token [String] the value to check
57
+ # @return [Boolean] whether the value is shaped like a session token
58
+ def self.session_token?(token)
59
+ SESSION_TOKEN_REGEX.match?(token.to_s)
60
+ end
61
+
47
62
  def self.set?(key = PRIMARY_KEY)
48
63
  Store.exists(key) == 1
49
64
  end
@@ -56,7 +71,7 @@ module OpenC3
56
71
  def self.verify(token, no_password: true, service_only: false)
57
72
  # Handle a service password - Generally only used by ScriptRunner
58
73
  # TODO: Replace this with temporary service tokens
59
- service_password = ENV['OPENC3_SERVICE_PASSWORD']
74
+ service_password = ENV.fetch('OPENC3_SERVICE_PASSWORD', nil)
60
75
  return true if service_password and service_password == token
61
76
 
62
77
  return false if service_only
@@ -78,15 +93,28 @@ module OpenC3
78
93
  # Check cached session tokens and password hash
79
94
  time = Time.now
80
95
  unless mode == :password
81
- if @@session_cache and (time - @@session_cache_time) < SESSION_CACHE_TIMEOUT and @@session_cache[token]
96
+ # Drop the whole cache once it ages out rather than tracking per token
97
+ # times. This bounds how long a terminated token keeps working to
98
+ # SESSION_CACHE_TIMEOUT, same as before.
99
+ if @@session_cache.nil? or (time - @@session_cache_time) >= SESSION_CACHE_TIMEOUT
100
+ @@session_cache = {}
101
+ @@session_cache_time = time
102
+ end
103
+
104
+ if @@session_cache[token]
82
105
  terminate_otp(token)
83
106
  return true
84
107
  end
85
108
 
86
- # Check stored session tokens
87
- @@session_cache = Store.hgetall(SESSIONS_KEY)
88
- @@session_cache_time = time
89
- if @@session_cache[token]
109
+ # Check the stored session tokens for just this token. Deliberately not
110
+ # HGETALL: verify_token is unauthenticated, so reading the entire
111
+ # session hash here lets any caller make us pull every session ever
112
+ # created on every request, and that hash only grows. HGET is O(1) and
113
+ # can't be amplified. The cache then fills with the tokens actually in
114
+ # use instead of all of them.
115
+ stored = Store.hget(SESSIONS_KEY, token)
116
+ if stored
117
+ @@session_cache[token] = stored
90
118
  terminate_otp(token)
91
119
  return true
92
120
  end
@@ -0,0 +1,133 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2026 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is distributed in the hope that it will be useful,
7
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
8
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ # See LICENSE.md for more details.
10
+ #
11
+ # This file may also be used under the terms of a commercial license
12
+ # if purchased from OpenC3, Inc.
13
+
14
+ require 'openc3/models/model'
15
+ require 'openc3/models/microservice_model'
16
+ require 'base64'
17
+ require 'json'
18
+ require 'securerandom'
19
+
20
+ module OpenC3
21
+ # Stores the identity of a named Iroh bridge (the bridge_microservice hub).
22
+ #
23
+ # A bridge is the COSMOS-side Iroh server that openc3-app and host interfaces
24
+ # connect to. This model stores the bridge's Iroh public key (its EndpointId)
25
+ # and most recent connection ticket, plus the authorized openc3-app control
26
+ # identity (app_public_key) recorded during enrollment. The bridge's own
27
+ # private key lives in the secrets store, never here.
28
+ #
29
+ # This Ruby model mirrors the Python BridgeModel (same scoped primary key and
30
+ # JSON shape) so the openc3cli `bridgeenroll` command can read and update the
31
+ # same records the Python BridgeMicroservice maintains.
32
+ class BridgeModel < Model
33
+ PRIMARY_KEY = 'openc3_bridges'
34
+ ENROLLMENT_CODE_TTL_SECONDS = 10 * 60
35
+
36
+ attr_accessor :public_key
37
+ attr_accessor :ticket
38
+ attr_accessor :app_public_key
39
+ attr_accessor :enroll_code
40
+ attr_accessor :enroll_code_generated_at
41
+ attr_accessor :port
42
+
43
+ # The bridge stack (bridge_microservice.py) is Python and runs from the core
44
+ # library regardless of the interfaces bridged through it.
45
+ def self.bridge_python_bin
46
+ ENV['OPENC3_PYTHON_BIN'] || '/openc3/python/.venv/bin/python'
47
+ end
48
+
49
+ BRIDGE_WORK_DIR = '/openc3/python/openc3/microservices'
50
+
51
+ # Build (but do not create) the MicroserviceModel for a bridge_microservice
52
+ # hub named `bridge_name` in `scope`. Centralizes the cmd/work_dir/options so
53
+ # ScopeModel, the DEFAULT-bridge migration, and InterfaceModel stay in sync.
54
+ def self.build_microservice(bridge_name:, scope:, parent: nil, shard: 0, plugin: nil)
55
+ microservice_name = "#{scope}__BRIDGE__#{bridge_name.to_s.upcase}"
56
+ MicroserviceModel.new(
57
+ name: microservice_name,
58
+ cmd: [bridge_python_bin, 'bridge_microservice.py', microservice_name],
59
+ work_dir: BRIDGE_WORK_DIR,
60
+ options: [['BRIDGE_NAME', bridge_name.to_s.upcase]],
61
+ parent: parent,
62
+ shard: shard.to_i,
63
+ plugin: plugin,
64
+ scope: scope
65
+ )
66
+ end
67
+
68
+ # NOTE: The following three class methods are reimplemented (like other
69
+ # scoped models) so the ModelController and Model class methods work.
70
+ def self.get(name:, scope:)
71
+ super("#{scope}__#{PRIMARY_KEY}", name: name)
72
+ end
73
+
74
+ def self.names(scope:)
75
+ super("#{scope}__#{PRIMARY_KEY}")
76
+ end
77
+
78
+ def self.all(scope:)
79
+ super("#{scope}__#{PRIMARY_KEY}")
80
+ end
81
+ # END NOTE
82
+
83
+ def initialize(
84
+ name:,
85
+ scope:,
86
+ public_key: nil,
87
+ ticket: nil,
88
+ app_public_key: nil,
89
+ enroll_code: nil,
90
+ enroll_code_generated_at: nil,
91
+ port: nil,
92
+ updated_at: nil,
93
+ plugin: nil
94
+ )
95
+ super("#{scope}__#{PRIMARY_KEY}", name: name, updated_at: updated_at, plugin: plugin, scope: scope)
96
+ @public_key = public_key
97
+ @ticket = ticket
98
+ @app_public_key = app_public_key
99
+ @enroll_code = enroll_code
100
+ @enroll_code_generated_at = enroll_code_generated_at
101
+ @port = port
102
+ end
103
+
104
+ def as_json(*_a)
105
+ {
106
+ 'name' => @name,
107
+ 'scope' => @scope,
108
+ 'updated_at' => @updated_at,
109
+ 'plugin' => @plugin,
110
+ 'public_key' => @public_key,
111
+ 'ticket' => @ticket,
112
+ 'app_public_key' => @app_public_key,
113
+ 'enroll_code' => @enroll_code,
114
+ 'enroll_code_generated_at' => @enroll_code_generated_at,
115
+ 'port' => @port
116
+ }
117
+ end
118
+
119
+ # Generate a one-time manual-enrollment code, store it on this bridge, and
120
+ # return a token (base64 JSON of bridge name + hub ticket + code) that a
121
+ # remote openc3-app pastes to enroll. Requires the bridge to be up (ticket
122
+ # present). The code is redeemed once over the api/enroll ALPN.
123
+ def generate_enrollment_token
124
+ raise "Bridge '#{@name}' has no ticket yet; is its bridge_microservice running?" if @ticket.nil? || @ticket.empty?
125
+
126
+ @enroll_code = SecureRandom.hex(16)
127
+ @enroll_code_generated_at = Time.now.to_i
128
+ update()
129
+ payload = { 'v' => 1, 'bridge' => @name, 'ticket' => @ticket, 'code' => @enroll_code }
130
+ Base64.urlsafe_encode64(JSON.generate(payload), padding: false)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: ascii-8bit
2
+
3
+ # Copyright 2026 OpenC3, Inc.
4
+ # All Rights Reserved.
5
+ #
6
+ # This program is distributed in the hope that it will be useful,
7
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
8
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
9
+ # See LICENSE.md for more details.
10
+ #
11
+ # This file may also be used under the terms of a commercial license
12
+ # if purchased from OpenC3, Inc.
13
+
14
+ require 'openc3/utilities/secrets'
15
+
16
+ # Mixin providing plugin.txt keyword handling shared by the models the operator
17
+ # turns into running containers (InterfaceModel / RouterModel and
18
+ # MicroserviceModel). Including classes must initialize @ports to an Array.
19
+ module OpenC3
20
+ module ConfigKeywordModel
21
+ # Parse a PORT keyword, appending [number, protocol] to @ports.
22
+ def parse_port(parser, keyword, parameters)
23
+ usage = "PORT <Number> <Protocol (Optional)"
24
+ parser.verify_num_parameters(1, 2, usage)
25
+ begin
26
+ @ports << [Integer(parameters[0])]
27
+ rescue # In case Integer fails
28
+ raise ConfigParser::Error.new(parser, "Port must be an integer: #{parameters[0]}", usage)
29
+ end
30
+ protocol = ConfigParser.handle_nil(parameters[1])
31
+ if protocol
32
+ # Per https://kubernetes.io/docs/concepts/services-networking/service/#protocol-support
33
+ if %w(TCP UDP SCTP).include?(protocol.upcase)
34
+ @ports[-1] << protocol.upcase
35
+ else
36
+ raise ConfigParser::Error.new(parser, "Unknown port protocol: #{parameters[1]}", usage)
37
+ end
38
+ else
39
+ @ports[-1] << 'TCP'
40
+ end
41
+ end
42
+
43
+ # Validate a SECRET / BRIDGE_SECRET definition. FILE type paths are restricted
44
+ # to Secrets.secret_file_dir to prevent reading or overwriting arbitrary files.
45
+ def validate_secret(parser, keyword, parameters)
46
+ type = parameters[0].to_s.upcase
47
+ unless ['ENV', 'FILE'].include?(type)
48
+ raise ConfigParser::Error.new(parser, "Unknown secret type '#{parameters[0]}' for #{keyword}. Must be ENV or FILE.")
49
+ end
50
+ # Normalize in place so the stored secret matches what the operator and
51
+ # Secrets.setup match on, both of which compare against 'ENV' / 'FILE' and
52
+ # use the path exactly as given.
53
+ parameters[0] = type
54
+ if type == 'FILE'
55
+ begin
56
+ parameters[2] = Secrets.validate_file_path(parameters[2])
57
+ rescue ArgumentError => error
58
+ raise ConfigParser::Error.new(parser, "#{keyword} #{error.message}")
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -86,7 +86,7 @@ module OpenC3
86
86
  return hash if hash and (now - cache_time) < cache_timeout
87
87
  end
88
88
  packet = store_for_target(target_name, scope: scope).hget(key, packet_name)
89
- raise "Packet '#{target_name} #{packet_name}' does not exist" unless packet
89
+ raise "Packet '#{target_name} #{packet_name}' has no current values in CVT" unless packet
90
90
  hash = JSON.parse(packet, allow_nan: true, create_additions: true)
91
91
  @@packet_cache[tgt_pkt_key] = [now, hash]
92
92
  hash
@@ -107,7 +107,7 @@ module OpenC3
107
107
  hash["#{item_name}__C"] = value
108
108
  hash[item_name] = value
109
109
  else
110
- raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
110
+ raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name} (set_item)"
111
111
  end
112
112
  set(hash, target_name: target_name, packet_name: packet_name, queued: queued, scope: scope)
113
113
  end
@@ -252,7 +252,7 @@ module OpenC3
252
252
  when :FORMATTED, :WITH_UNITS
253
253
  hash["#{item_name}__F"] = value.to_s # Always a String
254
254
  else
255
- raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
255
+ raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name} (override)"
256
256
  end
257
257
 
258
258
  tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
@@ -277,7 +277,7 @@ module OpenC3
277
277
  when :FORMATTED, :WITH_UNITS
278
278
  hash.delete("#{item_name}__F")
279
279
  else
280
- raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
280
+ raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name} (normalize)"
281
281
  end
282
282
 
283
283
  tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
@@ -327,7 +327,7 @@ module OpenC3
327
327
  when :RAW
328
328
  types = [item_name]
329
329
  else
330
- raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name}"
330
+ raise "Unknown type '#{type}' for #{target_name} #{packet_name} #{item_name} (get_item)"
331
331
  end
332
332
 
333
333
  tgt_pkt_key = "#{scope}__tlm__#{target_name}__#{packet_name}"
File without changes
File without changes