openc3 5.6.1 → 5.7.2
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.
Potentially problematic release.
This version of openc3 might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +4 -0
- data/bin/openc3cli +39 -6
- data/data/config/interface_modifiers.yaml +71 -0
- data/data/config/protocols.yaml +46 -0
- data/lib/openc3/interfaces/interface.rb +1 -0
- data/lib/openc3/interfaces/protocols/cobs_protocol.rb +120 -0
- data/lib/openc3/interfaces/protocols/slip_protocol.rb +150 -0
- data/lib/openc3/interfaces/protocols/terminated_protocol.rb +2 -1
- data/lib/openc3/io/json_api.rb +1 -1
- data/lib/openc3/microservices/interface_decom_common.rb +0 -1
- data/lib/openc3/models/interface_model.rb +75 -2
- data/lib/openc3/models/microservice_model.rb +1 -1
- data/lib/openc3/models/reaction_model.rb +0 -3
- data/lib/openc3/models/target_model.rb +1 -1
- data/lib/openc3/models/widget_model.rb +6 -2
- data/lib/openc3/operators/operator.rb +11 -9
- data/lib/openc3/packets/parsers/xtce_parser.rb +4 -2
- data/lib/openc3/script/api_shared.rb +27 -24
- data/lib/openc3/script/storage.rb +4 -0
- data/lib/openc3/tools/table_manager/table_manager_core.rb +4 -2
- data/lib/openc3/utilities/ruby_lex_utils.rb +4 -27
- data/lib/openc3/utilities/store_autoload.rb +1 -0
- data/lib/openc3/version.rb +6 -6
- data/templates/widget/package.json +8 -8
- data/templates/widget/yarn.lock +201 -155
- metadata +4 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b2926debd9479ec12631eab7287a0a4ff480c8c4957f7702de4c148f5e87a376
         | 
| 4 | 
            +
              data.tar.gz: 687f5c815c7016f34feb4f484cbcf582c8e5d3ef621098beb0d3aacf612c49c0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 4a33e7fe434b726dc9231bcf066d112114ca3e8109496ee4c2742b8925d4c3297f22a7e3d43ec5592ba298969eafd9e37713754efc1556f257ae9b584e7ef38c
         | 
| 7 | 
            +
              data.tar.gz: b552b42d338e23625ed78f7fb429deb59a456bd5ab7c783930fe050d8c5ec360c39e2630f39f005c9b370c5f3a49f8d943a04951990edb8e261c5d5e1ab0bd71
         | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/bin/openc3cli
    CHANGED
    
    | @@ -141,18 +141,21 @@ def migrate(args) | |
| 141 141 |  | 
| 142 142 | 
             
              # Overwrite plugin.txt with specified targets
         | 
| 143 143 | 
             
              plugin = File.open('plugin.txt', 'w')
         | 
| 144 | 
            -
               | 
| 145 | 
            -
             | 
| 144 | 
            +
              FileUtils.mkdir 'targets'
         | 
| 146 145 | 
             
              args.each do |target|
         | 
| 147 146 | 
             
                puts "Migrating target #{target}"
         | 
| 148 147 | 
             
                FileUtils.cp_r "../config/targets/#{target}", 'targets'
         | 
| 149 | 
            -
                plugin.puts "TARGET #{target}"
         | 
| 148 | 
            +
                plugin.puts "TARGET #{target} #{target}"
         | 
| 150 149 | 
             
              end
         | 
| 151 150 | 
             
              plugin.puts ""
         | 
| 152 151 |  | 
| 153 | 
            -
               | 
| 154 | 
            -
               | 
| 155 | 
            -
               | 
| 152 | 
            +
              files = Dir.glob('../lib/*')
         | 
| 153 | 
            +
              files.concat(Dir.glob('../procedures/*'))
         | 
| 154 | 
            +
              unless files.empty?
         | 
| 155 | 
            +
                puts "Migrating /lib & /procedures to PROCEDURES target"
         | 
| 156 | 
            +
                FileUtils.cp_r '../lib', "targets/PROCEDURES"
         | 
| 157 | 
            +
                FileUtils.cp_r '../procedures', "targets/PROCEDURES"
         | 
| 158 | 
            +
              end
         | 
| 156 159 |  | 
| 157 160 | 
             
              # Migrate cmd_tlm_server.txt info to plugin.txt
         | 
| 158 161 | 
             
              Dir.glob('targets/**/cmd_tlm_server*.txt') do |file|
         | 
| @@ -168,6 +171,36 @@ def migrate(args) | |
| 168 171 | 
             
                end
         | 
| 169 172 | 
             
                plugin.puts ''
         | 
| 170 173 | 
             
              end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
              # Migrate target.txt
         | 
| 176 | 
            +
              Dir.glob('targets/**/target.txt') do |filename|
         | 
| 177 | 
            +
                file = File.read(filename)
         | 
| 178 | 
            +
                file.gsub!('LOG_RAW', 'LOG_STREAM')
         | 
| 179 | 
            +
                file.gsub!('AUTO_SCREEN_SUBSTITUTE', '')
         | 
| 180 | 
            +
                File.write(filename, file)
         | 
| 181 | 
            +
              end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
              # Migrate some of the screens api
         | 
| 184 | 
            +
              Dir.glob('targets/**/screens/*') do |file|
         | 
| 185 | 
            +
                screen = File.read(file)
         | 
| 186 | 
            +
                screen.gsub!('cmd(', 'api.cmd(')
         | 
| 187 | 
            +
                screen.gsub!('cmd_no_checks(', 'api.cmd_no_checks(')
         | 
| 188 | 
            +
                screen.gsub!('cmd_no_range_check(', 'api.cmd_no_range_check(')
         | 
| 189 | 
            +
                screen.gsub!('cmd_no_hazardous_check(', 'api.cmd_no_hazardous_check(')
         | 
| 190 | 
            +
                screen.gsub!('get_named_widget(', 'screen.getNamedWidget(')
         | 
| 191 | 
            +
                lines = screen.split("\n")
         | 
| 192 | 
            +
                lines.map! do |line|
         | 
| 193 | 
            +
                  if line.include?('Qt.')
         | 
| 194 | 
            +
                    line = "# FIXME (no Qt): #{line.sub("<%", "< %").sub("%>", "% >")}"
         | 
| 195 | 
            +
                  elsif line.include?('Cosmos::')
         | 
| 196 | 
            +
                    line = "# FIXME (no Cosmos::): #{line.sub("<%", "< %").sub("%>", "% >")}"
         | 
| 197 | 
            +
                  else
         | 
| 198 | 
            +
                    line
         | 
| 199 | 
            +
                  end
         | 
| 200 | 
            +
                end
         | 
| 201 | 
            +
                File.write(file, lines.join("\n"))
         | 
| 202 | 
            +
              end
         | 
| 203 | 
            +
             | 
| 171 204 | 
             
              plugin.close
         | 
| 172 205 | 
             
              puts "Plugin complete: #{File.expand_path('.')}" # Remember we're inside the plugin dir
         | 
| 173 206 | 
             
            end
         | 
| @@ -168,3 +168,74 @@ SECRET: | |
| 168 168 | 
             
              example: |
         | 
| 169 169 | 
             
                SECRET ENV USERNAME ENV_USERNAME USERNAME
         | 
| 170 170 | 
             
                SECRET FILE KEY "/tmp/DATA/cert" KEY
         | 
| 171 | 
            +
            ENV:
         | 
| 172 | 
            +
              summary: Sets an environment variable in the microservice.
         | 
| 173 | 
            +
              since: 5.7.0
         | 
| 174 | 
            +
              parameters:
         | 
| 175 | 
            +
                - name: Key
         | 
| 176 | 
            +
                  required: true
         | 
| 177 | 
            +
                  description: Environment variable name
         | 
| 178 | 
            +
                  values: .+
         | 
| 179 | 
            +
                - name: Value
         | 
| 180 | 
            +
                  required: true
         | 
| 181 | 
            +
                  description: Environment variable value
         | 
| 182 | 
            +
                  values: .+
         | 
| 183 | 
            +
              example: |
         | 
| 184 | 
            +
                ENV COMPANY OpenC3
         | 
| 185 | 
            +
            WORK_DIR:
         | 
| 186 | 
            +
              summary: Set the working directory
         | 
| 187 | 
            +
              description: Working directory to run the microservice CMD in.  Can be a path relative to the microservice folder in the plugin, or an absolute path in the container the microservice runs in.
         | 
| 188 | 
            +
              since: 5.7.0
         | 
| 189 | 
            +
              parameters:
         | 
| 190 | 
            +
                - name: Directory
         | 
| 191 | 
            +
                  required: true
         | 
| 192 | 
            +
                  description: Working directory to run the microservice CMD in. Can be a path relative to the microservice folder in the plugin, or an absolute path in the container the microservice runs in.
         | 
| 193 | 
            +
                  values: .+
         | 
| 194 | 
            +
              example: |
         | 
| 195 | 
            +
                WORK_DIR '/openc3/lib/openc3/microservices'
         | 
| 196 | 
            +
            PORT:
         | 
| 197 | 
            +
              summary: Open port for the microservice
         | 
| 198 | 
            +
              description: Kubernetes needs a Service to be applied to open a port so this is required for Kubernetes support
         | 
| 199 | 
            +
              since: 5.7.0
         | 
| 200 | 
            +
              parameters:
         | 
| 201 | 
            +
                - name: Number
         | 
| 202 | 
            +
                  required: true
         | 
| 203 | 
            +
                  description: Port number
         | 
| 204 | 
            +
                  values: \d+
         | 
| 205 | 
            +
                - name: Protocol
         | 
| 206 | 
            +
                  required: false
         | 
| 207 | 
            +
                  description: Port protocol. Default is TCP.
         | 
| 208 | 
            +
                  values: .+
         | 
| 209 | 
            +
              example: |
         | 
| 210 | 
            +
                PORT 7272
         | 
| 211 | 
            +
            CMD:
         | 
| 212 | 
            +
              summary: Command line to execute to run the microservice.
         | 
| 213 | 
            +
              description: Command line to execute to run the microservice.
         | 
| 214 | 
            +
              since: 5.7.0
         | 
| 215 | 
            +
              parameters:
         | 
| 216 | 
            +
                - name: Args
         | 
| 217 | 
            +
                  required: true
         | 
| 218 | 
            +
                  description: One or more arguments to exec to run the microservice.
         | 
| 219 | 
            +
                  values: .+
         | 
| 220 | 
            +
              example: |
         | 
| 221 | 
            +
                CMD ruby interface_microservice.rb DEFAULT__INTERFACE__INT1
         | 
| 222 | 
            +
            CONTAINER:
         | 
| 223 | 
            +
              summary: Docker Container
         | 
| 224 | 
            +
              description: Container to execute and run the microservice in. Only used in COSMOS Enterprise Edition.
         | 
| 225 | 
            +
              since: 5.7.0
         | 
| 226 | 
            +
              parameters:
         | 
| 227 | 
            +
                - name: Args
         | 
| 228 | 
            +
                  required: false
         | 
| 229 | 
            +
                  description: Name of the container
         | 
| 230 | 
            +
                  values: .+
         | 
| 231 | 
            +
            ROUTE_PREFIX:
         | 
| 232 | 
            +
              summary: Prefix of route
         | 
| 233 | 
            +
              description: Prefix of route to the microservice to expose externally with Traefik
         | 
| 234 | 
            +
              since: 5.7.0
         | 
| 235 | 
            +
              parameters:
         | 
| 236 | 
            +
                - name: Route Prefix
         | 
| 237 | 
            +
                  required: true
         | 
| 238 | 
            +
                  description: Route prefix. Must be unique across all scopes. Something like /myprefix
         | 
| 239 | 
            +
                  values: .*
         | 
| 240 | 
            +
              example: |
         | 
| 241 | 
            +
                ROUTE_PREFIX /interface
         | 
    
        data/data/config/protocols.yaml
    CHANGED
    
    | @@ -288,3 +288,49 @@ TEMPLATE: | |
| 288 288 | 
             
                    or response timeouts. 'DISCONNECT' to disconnect after errors. The default
         | 
| 289 289 | 
             
                    is 'LOG' to log an error and continue.
         | 
| 290 290 | 
             
                  values: ["LOG", "DISCONNECT"]
         | 
| 291 | 
            +
            SLIP:
         | 
| 292 | 
            +
              description:
         | 
| 293 | 
            +
                The SLIP Protocol implements RFC 1055. This is a terminated protocol which terminates
         | 
| 294 | 
            +
                with a 0xC0 character, and escapes internally conflicting bytes.
         | 
| 295 | 
            +
              parameters:
         | 
| 296 | 
            +
                - name: Start Character
         | 
| 297 | 
            +
                  required: false
         | 
| 298 | 
            +
                  description:
         | 
| 299 | 
            +
                    Character to place at the beginning of a packet.  Defaults to nil. Some variants of the
         | 
| 300 | 
            +
                    SLIP Protocol also place a 0xC0 byte at the beginning of packets.
         | 
| 301 | 
            +
                  values: \d+
         | 
| 302 | 
            +
                - name: Read Strip Characters
         | 
| 303 | 
            +
                  required: false
         | 
| 304 | 
            +
                  description:
         | 
| 305 | 
            +
                    Whether or not to strip the start and end characters out of the packet when reading. Defaults
         | 
| 306 | 
            +
                    to true.
         | 
| 307 | 
            +
                  values: ["true", "false"]
         | 
| 308 | 
            +
                - name: Read Enable Escaping
         | 
| 309 | 
            +
                  required: false
         | 
| 310 | 
            +
                  description: Whether or not to escape conflicting characters in the packet om reads. Defaults to true.
         | 
| 311 | 
            +
                  values: ["true", "false"]
         | 
| 312 | 
            +
                - name: Write Enable Escaping
         | 
| 313 | 
            +
                  required: false
         | 
| 314 | 
            +
                  description: Whether or not to escape conflicting characters in the packet on writes. Defaults to true.
         | 
| 315 | 
            +
                  values: ["true", "false"]
         | 
| 316 | 
            +
                - name: End Character
         | 
| 317 | 
            +
                  required: false
         | 
| 318 | 
            +
                  description: Character to end packets with. Defaults to 0xC0.
         | 
| 319 | 
            +
                  values: \d+
         | 
| 320 | 
            +
                - name: Escape Character
         | 
| 321 | 
            +
                  required: false
         | 
| 322 | 
            +
                  description: Character that indicates an escape sequence. Defaults to 0xDB.
         | 
| 323 | 
            +
                  values: \d+
         | 
| 324 | 
            +
                - name: Escaped End Character
         | 
| 325 | 
            +
                  required: false
         | 
| 326 | 
            +
                  description: Escaped version of the end character. Defaults to 0xDC.
         | 
| 327 | 
            +
                  values: \d+
         | 
| 328 | 
            +
                - name: Escaped Escape Character
         | 
| 329 | 
            +
                  required: false
         | 
| 330 | 
            +
                  description: Escaped version of the escape character. Defaults to 0xDD.
         | 
| 331 | 
            +
                  values: \d+
         | 
| 332 | 
            +
            COBS:
         | 
| 333 | 
            +
              description:
         | 
| 334 | 
            +
                The COBS Protocol implements the Consistent Overhead Byte Stuffing Protocol. This is a terminated protocol which terminates
         | 
| 335 | 
            +
                with a 0x00 character, and escapes internal 0's using a unique algorithm that only adds one byte of overhead for every
         | 
| 336 | 
            +
                254 bytes.
         | 
| @@ -0,0 +1,120 @@ | |
| 1 | 
            +
            # encoding: ascii-8bit
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Copyright 2023 OpenC3, Inc.
         | 
| 4 | 
            +
            # All Rights Reserved.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # This program is free software; you can modify and/or redistribute it
         | 
| 7 | 
            +
            # under the terms of the GNU Affero General Public License
         | 
| 8 | 
            +
            # as published by the Free Software Foundation; version 3 with
         | 
| 9 | 
            +
            # attribution addendums as found in the LICENSE.txt
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            # This program is distributed in the hope that it will be useful,
         | 
| 12 | 
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 13 | 
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
         | 
| 14 | 
            +
            # GNU Affero General Public License for more details.
         | 
| 15 | 
            +
            #
         | 
| 16 | 
            +
            # This file may also be used under the terms of a commercial license
         | 
| 17 | 
            +
            # if purchased from OpenC3, Inc.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            require 'openc3/interfaces/protocols/terminated_protocol'
         | 
| 20 | 
            +
            require 'openc3/config/config_parser'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            # This file implements the COBS protocol as here:
         | 
| 23 | 
            +
            # https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing
         | 
| 24 | 
            +
            # http://www.stuartcheshire.org/papers/COBSforToN.pdf
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            # COBS is a framing protocol and is therefore expected to be used for packet deliniation
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            module OpenC3
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              # Usage in plugin.txt:
         | 
| 31 | 
            +
              #
         | 
| 32 | 
            +
              # INTERFACE ...
         | 
| 33 | 
            +
              #   PROTOCOL READ_WRITE CobsProtocol
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              class CobsProtocol < TerminatedProtocol
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                # @param allow_empty_data [true/false/nil] See Protocol#initialize
         | 
| 38 | 
            +
                def initialize(allow_empty_data = nil)
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  strip_read_termination = true
         | 
| 41 | 
            +
                  discard_leading_bytes = 0
         | 
| 42 | 
            +
                  sync_pattern = nil
         | 
| 43 | 
            +
                  fill_fields = false # Handled in write_data below
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  super(
         | 
| 46 | 
            +
                    "", # Write termination handled in write_data below
         | 
| 47 | 
            +
                    "00",
         | 
| 48 | 
            +
                    strip_read_termination,
         | 
| 49 | 
            +
                    discard_leading_bytes,
         | 
| 50 | 
            +
                    sync_pattern,
         | 
| 51 | 
            +
                    fill_fields,
         | 
| 52 | 
            +
                    allow_empty_data
         | 
| 53 | 
            +
                  )
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def read_data(data)
         | 
| 57 | 
            +
                  data = super(data)
         | 
| 58 | 
            +
                  return data if data.length <= 0 or Symbol === data
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  result_data = ''
         | 
| 61 | 
            +
                  while data.length > 1
         | 
| 62 | 
            +
                    # Read the offset to the next zero byte
         | 
| 63 | 
            +
                    # Note: This may be off the end of the data. If so, the packet is over
         | 
| 64 | 
            +
                    zero_offset = data[0].unpack('C')[0]
         | 
| 65 | 
            +
                    if zero_offset == 0xFF # No zeros in this segment
         | 
| 66 | 
            +
                      result_data << data[1..254]
         | 
| 67 | 
            +
                      data = data[255..-1]
         | 
| 68 | 
            +
                    elsif zero_offset <= 1 # End of data or 1 zero
         | 
| 69 | 
            +
                      result_data << "\x00"
         | 
| 70 | 
            +
                      data = data[1..-1]
         | 
| 71 | 
            +
                    else # Mid range zero or end of packet
         | 
| 72 | 
            +
                      result_data << data[1..(zero_offset - 1)]
         | 
| 73 | 
            +
                      data = data[zero_offset..-1]
         | 
| 74 | 
            +
                      result_data << "\x00" if data.length >= 1
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  return result_data
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                def write_data(data)
         | 
| 82 | 
            +
                  # Intentionally not calling super()
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  need_insert = false
         | 
| 85 | 
            +
                  result_data = ''
         | 
| 86 | 
            +
                  while data.length > 0
         | 
| 87 | 
            +
                    index = data.index("\x00")
         | 
| 88 | 
            +
                    if (index and index > 253) or (index.nil? and data.length >= 254)
         | 
| 89 | 
            +
                      result_data << "\xFF"
         | 
| 90 | 
            +
                      result_data << data[0..253]
         | 
| 91 | 
            +
                      data = data[254..-1]
         | 
| 92 | 
            +
                      need_insert = false
         | 
| 93 | 
            +
                    else # index <= 254 or (index.nil? and data.length < 254)
         | 
| 94 | 
            +
                      if index
         | 
| 95 | 
            +
                        result_data << [index + 1].pack('C')
         | 
| 96 | 
            +
                        if index >= 1
         | 
| 97 | 
            +
                          result_data << data[0..(index - 1)]
         | 
| 98 | 
            +
                        end
         | 
| 99 | 
            +
                        data = data[(index + 1)..-1]
         | 
| 100 | 
            +
                        need_insert = true
         | 
| 101 | 
            +
                      else
         | 
| 102 | 
            +
                        result_data << [data.length + 1].pack('C')
         | 
| 103 | 
            +
                        result_data << data
         | 
| 104 | 
            +
                        data = ''
         | 
| 105 | 
            +
                        need_insert = false
         | 
| 106 | 
            +
                      end
         | 
| 107 | 
            +
                    end
         | 
| 108 | 
            +
                  end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  # Handle a zero at the end of the packet
         | 
| 111 | 
            +
                  result_data << "\x01" if need_insert
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  # Terminate message with 0x00
         | 
| 114 | 
            +
                  result_data << "\x00"
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                  return result_data
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
              end
         | 
| 119 | 
            +
             | 
| 120 | 
            +
            end
         | 
| @@ -0,0 +1,150 @@ | |
| 1 | 
            +
            # encoding: ascii-8bit
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Copyright 2023 OpenC3, Inc.
         | 
| 4 | 
            +
            # All Rights Reserved.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # This program is free software; you can modify and/or redistribute it
         | 
| 7 | 
            +
            # under the terms of the GNU Affero General Public License
         | 
| 8 | 
            +
            # as published by the Free Software Foundation; version 3 with
         | 
| 9 | 
            +
            # attribution addendums as found in the LICENSE.txt
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            # This program is distributed in the hope that it will be useful,
         | 
| 12 | 
            +
            # but WITHOUT ANY WARRANTY; without even the implied warranty of
         | 
| 13 | 
            +
            # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
         | 
| 14 | 
            +
            # GNU Affero General Public License for more details.
         | 
| 15 | 
            +
            #
         | 
| 16 | 
            +
            # This file may also be used under the terms of a commercial license
         | 
| 17 | 
            +
            # if purchased from OpenC3, Inc.
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            require 'openc3/interfaces/protocols/terminated_protocol'
         | 
| 20 | 
            +
            require 'openc3/config/config_parser'
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            # This file implements the SLIP protocol as documented in RFC 1055
         | 
| 23 | 
            +
            # https://datatracker.ietf.org/doc/html/rfc1055
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            # SLIP is a framing protocol and is therefore expected to be used for packet deliniation
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            module OpenC3
         | 
| 28 | 
            +
             | 
| 29 | 
            +
              # Usage in plugin.txt:
         | 
| 30 | 
            +
              #
         | 
| 31 | 
            +
              # INTERFACE ...
         | 
| 32 | 
            +
              #   PROTOCOL READ_WRITE SlipProtocol
         | 
| 33 | 
            +
             | 
| 34 | 
            +
              class SlipProtocol < TerminatedProtocol
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                # Note: Characters are expected to be given as integers
         | 
| 37 | 
            +
                # @param start_char   [Integer/nil] Character to place at the start of frames (Defaults to nil)
         | 
| 38 | 
            +
                # @param read_strip_characters [true/false] Strip off start_char and end_char from reads
         | 
| 39 | 
            +
                # @param read_enable_escaping [true/false] Whether to enable or disable character escaping on reads
         | 
| 40 | 
            +
                # @param write_enable_escaping [true/false] Whether to enable or disable character escaping on writes
         | 
| 41 | 
            +
                # @param end_char     [Integer] Character to place at the end of frames (Defaults to 0xC0)
         | 
| 42 | 
            +
                # @param esc_char     [Integer] Escape character (Defaults to 0xDB)
         | 
| 43 | 
            +
                # @param esc_end_char [Integer] Character to Escape End character (Defaults to 0xDC)
         | 
| 44 | 
            +
                # @param esc_esc_char [Integer] Character to Escape Escape character (Defaults to 0xDD)
         | 
| 45 | 
            +
                # @param allow_empty_data [true/false/nil] See Protocol#initialize
         | 
| 46 | 
            +
                def initialize(
         | 
| 47 | 
            +
                  start_char = nil,
         | 
| 48 | 
            +
                  read_strip_characters = true,
         | 
| 49 | 
            +
                  read_enable_escaping = true,
         | 
| 50 | 
            +
                  write_enable_escaping = true,
         | 
| 51 | 
            +
                  end_char = 0xC0,
         | 
| 52 | 
            +
                  esc_char = 0xDB,
         | 
| 53 | 
            +
                  esc_end_char = 0xDC,
         | 
| 54 | 
            +
                  esc_esc_char = 0xDD,
         | 
| 55 | 
            +
                  allow_empty_data = nil)
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                  @start_char = ConfigParser.handle_nil(start_char)
         | 
| 58 | 
            +
                  @start_char = [Integer(end_char)].pack('C') if @start_char
         | 
| 59 | 
            +
                  @end_char = [Integer(end_char)].pack('C')
         | 
| 60 | 
            +
                  @esc_char = [Integer(esc_char)].pack('C')
         | 
| 61 | 
            +
                  @esc_end_char = [Integer(esc_end_char)].pack('C')
         | 
| 62 | 
            +
                  @esc_esc_char = [Integer(esc_esc_char)].pack('C')
         | 
| 63 | 
            +
                  @replace_end = @esc_char + @esc_end_char
         | 
| 64 | 
            +
                  @replace_esc = @esc_char + @esc_esc_char
         | 
| 65 | 
            +
                  @read_strip_characters = ConfigParser.handle_true_false(read_strip_characters)
         | 
| 66 | 
            +
                  raise "read_strip_characters must be true or false" if @read_strip_characters != true and @read_strip_characters != false
         | 
| 67 | 
            +
                  @read_enable_escaping = ConfigParser.handle_true_false(read_enable_escaping)
         | 
| 68 | 
            +
                  raise "read_enable_escaping must be true or false" if @read_enable_escaping != true and @read_enable_escaping != false
         | 
| 69 | 
            +
                  @write_enable_escaping = ConfigParser.handle_true_false(write_enable_escaping)
         | 
| 70 | 
            +
                  raise "write_enable_escaping must be true or false" if @write_enable_escaping != true and @write_enable_escaping != false
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  strip_read_termination = false
         | 
| 73 | 
            +
                  discard_leading_bytes = 0
         | 
| 74 | 
            +
                  if @start_char
         | 
| 75 | 
            +
                    sync_pattern = sprintf("%0X", Integer(start_char))
         | 
| 76 | 
            +
                  else
         | 
| 77 | 
            +
                    sync_pattern = nil
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
                  fill_fields = false # Handled in write_data below
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  super(
         | 
| 82 | 
            +
                    "", # Write termination handled in write_data below
         | 
| 83 | 
            +
                    sprintf("%0X", Integer(end_char)), # Expects Hex Character String
         | 
| 84 | 
            +
                    strip_read_termination,
         | 
| 85 | 
            +
                    discard_leading_bytes,
         | 
| 86 | 
            +
                    sync_pattern,
         | 
| 87 | 
            +
                    fill_fields,
         | 
| 88 | 
            +
                    allow_empty_data
         | 
| 89 | 
            +
                  )
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def read_data(data)
         | 
| 93 | 
            +
                  data = super(data)
         | 
| 94 | 
            +
                  return data if data.length <= 0 or Symbol === data
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  if @read_strip_characters
         | 
| 97 | 
            +
                    if @start_char
         | 
| 98 | 
            +
                      data = data[1..-1]
         | 
| 99 | 
            +
                    end
         | 
| 100 | 
            +
                    data = data[0..-2]
         | 
| 101 | 
            +
                  end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                  if @read_enable_escaping
         | 
| 104 | 
            +
                    data = data.gsub(@replace_end, @end_char).gsub(@replace_esc, @esc_char)
         | 
| 105 | 
            +
                  end
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  return data
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                def write_data(data)
         | 
| 111 | 
            +
                  # Intentionally not calling super()
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  if @write_enable_escaping
         | 
| 114 | 
            +
                    data = data.gsub(@esc_char, @replace_esc).gsub(@end_char, @replace_end)
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  if @start_char
         | 
| 118 | 
            +
                    data = @start_char + data
         | 
| 119 | 
            +
                  end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                  data << @end_char
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                  return data
         | 
| 124 | 
            +
                end
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                def reduce_to_single_packet
         | 
| 127 | 
            +
                  return :STOP if @data.length <= 0
         | 
| 128 | 
            +
                  if @start_char
         | 
| 129 | 
            +
                    index = @data[1..-1].index(@read_termination_characters)
         | 
| 130 | 
            +
                    index = index + 1 if index
         | 
| 131 | 
            +
                  else
         | 
| 132 | 
            +
                    index = @data.index(@read_termination_characters)
         | 
| 133 | 
            +
                  end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                  # Reduce to packet data and setup current_data for next packet
         | 
| 136 | 
            +
                  if index
         | 
| 137 | 
            +
                    if index > 0
         | 
| 138 | 
            +
                      packet_data = @data[0..(index + @read_termination_characters.length - 1)]
         | 
| 139 | 
            +
                    else # @data begins with the termination characters
         | 
| 140 | 
            +
                      packet_data = @data[0..(@read_termination_characters.length - 1)]
         | 
| 141 | 
            +
                    end
         | 
| 142 | 
            +
                    @data.replace(@data[(index + @read_termination_characters.length)..-1])
         | 
| 143 | 
            +
                    return packet_data
         | 
| 144 | 
            +
                  else
         | 
| 145 | 
            +
                    return :STOP
         | 
| 146 | 
            +
                  end
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
              end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
            end
         | 
| @@ -17,7 +17,7 @@ | |
| 17 17 | 
             
            # All changes Copyright 2022, OpenC3, Inc.
         | 
| 18 18 | 
             
            # All Rights Reserved
         | 
| 19 19 | 
             
            #
         | 
| 20 | 
            -
            # This file may also be used under the terms of a commercial license | 
| 20 | 
            +
            # This file may also be used under the terms of a commercial license
         | 
| 21 21 | 
             
            # if purchased from OpenC3, Inc.
         | 
| 22 22 |  | 
| 23 23 | 
             
            require 'openc3/config/config_parser'
         | 
| @@ -52,6 +52,7 @@ module OpenC3 | |
| 52 52 | 
             
                  @write_termination_characters = write_termination_characters.hex_to_byte_string
         | 
| 53 53 | 
             
                  @read_termination_characters = read_termination_characters.hex_to_byte_string
         | 
| 54 54 | 
             
                  @strip_read_termination = ConfigParser.handle_true_false(strip_read_termination)
         | 
| 55 | 
            +
                  raise "strip_read_termination must be true or false" if @strip_read_termination != true and @strip_read_termination != false
         | 
| 55 56 |  | 
| 56 57 | 
             
                  super(discard_leading_bytes, sync_pattern, fill_fields, allow_empty_data)
         | 
| 57 58 | 
             
                end
         | 
    
        data/lib/openc3/io/json_api.rb
    CHANGED
    
    | @@ -65,7 +65,7 @@ module OpenC3 | |
| 65 65 |  | 
| 66 66 | 
             
                def _request(*method_params, **kw_params)
         | 
| 67 67 | 
             
                  kw_params[:scope] = $openc3_scope unless kw_params[:scope]
         | 
| 68 | 
            -
                  kw_params[:json]  | 
| 68 | 
            +
                  kw_params[:json] = true unless kw_params[:json]
         | 
| 69 69 | 
             
                  @json_api.request(*method_params, **kw_params)
         | 
| 70 70 | 
             
                end
         | 
| 71 71 | 
             
              end
         | 
| @@ -44,6 +44,12 @@ module OpenC3 | |
| 44 44 | 
             
                attr_accessor :log_stream
         | 
| 45 45 | 
             
                attr_accessor :needs_dependencies
         | 
| 46 46 | 
             
                attr_accessor :secrets
         | 
| 47 | 
            +
                attr_accessor :cmd
         | 
| 48 | 
            +
                attr_accessor :container
         | 
| 49 | 
            +
                attr_accessor :env
         | 
| 50 | 
            +
                attr_accessor :work_dir
         | 
| 51 | 
            +
                attr_accessor :ports
         | 
| 52 | 
            +
                attr_accessor :prefix
         | 
| 47 53 |  | 
| 48 54 | 
             
                # NOTE: The following three class methods are used by the ModelController
         | 
| 49 55 | 
             
                # and are reimplemented to enable various Model class methods to work
         | 
| @@ -109,6 +115,12 @@ module OpenC3 | |
| 109 115 | 
             
                  plugin: nil,
         | 
| 110 116 | 
             
                  needs_dependencies: false,
         | 
| 111 117 | 
             
                  secrets: [],
         | 
| 118 | 
            +
                  cmd: nil,
         | 
| 119 | 
            +
                  work_dir: '/openc3/lib/openc3/microservices',
         | 
| 120 | 
            +
                  ports: [],
         | 
| 121 | 
            +
                  env: {},
         | 
| 122 | 
            +
                  container: nil,
         | 
| 123 | 
            +
                  prefix: nil,
         | 
| 112 124 | 
             
                  scope:
         | 
| 113 125 | 
             
                )
         | 
| 114 126 | 
             
                  if self.class._get_type == 'INTERFACE'
         | 
| @@ -129,6 +141,17 @@ module OpenC3 | |
| 129 141 | 
             
                  @protocols = protocols
         | 
| 130 142 | 
             
                  @log_stream = log_stream
         | 
| 131 143 | 
             
                  @needs_dependencies = needs_dependencies
         | 
| 144 | 
            +
                  @cmd = cmd
         | 
| 145 | 
            +
                  unless @cmd
         | 
| 146 | 
            +
                    type = self.class._get_type
         | 
| 147 | 
            +
                    microservice_name = "#{@scope}__#{type}__#{@name}"
         | 
| 148 | 
            +
                    @cmd = ["ruby", "#{type.downcase}_microservice.rb", microservice_name]
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
                  @work_dir = work_dir
         | 
| 151 | 
            +
                  @ports = ports
         | 
| 152 | 
            +
                  @env = env
         | 
| 153 | 
            +
                  @container = container
         | 
| 154 | 
            +
                  @prefix = prefix
         | 
| 132 155 | 
             
                  @secrets = secrets
         | 
| 133 156 | 
             
                end
         | 
| 134 157 |  | 
| @@ -187,6 +210,12 @@ module OpenC3 | |
| 187 210 | 
             
                    'plugin' => @plugin,
         | 
| 188 211 | 
             
                    'needs_dependencies' => @needs_dependencies,
         | 
| 189 212 | 
             
                    'secrets' => @secrets.as_json(*a),
         | 
| 213 | 
            +
                    'cmd' => @cmd,
         | 
| 214 | 
            +
                    'work_dir' => @work_dir,
         | 
| 215 | 
            +
                    'ports' => @ports,
         | 
| 216 | 
            +
                    'env' => @env,
         | 
| 217 | 
            +
                    'container' => @container,
         | 
| 218 | 
            +
                    'prefix' => @prefix,
         | 
| 190 219 | 
             
                    'updated_at' => @updated_at
         | 
| 191 220 | 
             
                  }
         | 
| 192 221 | 
             
                end
         | 
| @@ -266,6 +295,46 @@ module OpenC3 | |
| 266 295 | 
             
                      @secrets[-1] << parameters[4]
         | 
| 267 296 | 
             
                    end
         | 
| 268 297 |  | 
| 298 | 
            +
                  when 'ENV'
         | 
| 299 | 
            +
                    parser.verify_num_parameters(2, 2, "#{keyword} <Key> <Value>")
         | 
| 300 | 
            +
                    @env[parameters[0]] = parameters[1]
         | 
| 301 | 
            +
             | 
| 302 | 
            +
                  when 'PORT'
         | 
| 303 | 
            +
                    usage = "PORT <Number> <Protocol (Optional)"
         | 
| 304 | 
            +
                    parser.verify_num_parameters(1, 2, usage)
         | 
| 305 | 
            +
                    begin
         | 
| 306 | 
            +
                      @ports << [Integer(parameters[0])]
         | 
| 307 | 
            +
                    rescue # In case Integer fails
         | 
| 308 | 
            +
                      raise ConfigParser::Error.new(parser, "Port must be an integer: #{parameters[0]}", usage)
         | 
| 309 | 
            +
                    end
         | 
| 310 | 
            +
                    protocol = ConfigParser.handle_nil(parameters[1])
         | 
| 311 | 
            +
                    if protocol
         | 
| 312 | 
            +
                      # Per https://kubernetes.io/docs/concepts/services-networking/service/#protocol-support
         | 
| 313 | 
            +
                      if %w(TCP UDP SCTP).include?(protocol.upcase)
         | 
| 314 | 
            +
                        @ports[-1] << protocol.upcase
         | 
| 315 | 
            +
                      else
         | 
| 316 | 
            +
                        raise ConfigParser::Error.new(parser, "Unknown port protocol: #{parameters[1]}", usage)
         | 
| 317 | 
            +
                      end
         | 
| 318 | 
            +
                    else
         | 
| 319 | 
            +
                      @ports[-1] << 'TCP'
         | 
| 320 | 
            +
                    end
         | 
| 321 | 
            +
             | 
| 322 | 
            +
                  when 'WORK_DIR'
         | 
| 323 | 
            +
                    parser.verify_num_parameters(1, 1, "#{keyword} <Dir>")
         | 
| 324 | 
            +
                    @work_dir = parameters[0]
         | 
| 325 | 
            +
             | 
| 326 | 
            +
                  when 'CMD'
         | 
| 327 | 
            +
                    parser.verify_num_parameters(1, nil, "#{keyword} <Args>")
         | 
| 328 | 
            +
                    @cmd = parameters.dup
         | 
| 329 | 
            +
             | 
| 330 | 
            +
                  when 'CONTAINER'
         | 
| 331 | 
            +
                    parser.verify_num_parameters(1, 1, "#{keyword} <Container Image Name>")
         | 
| 332 | 
            +
                    @container = parameters[0]
         | 
| 333 | 
            +
             | 
| 334 | 
            +
                  when 'ROUTE_PREFIX'
         | 
| 335 | 
            +
                    parser.verify_num_parameters(1, 1, "#{keyword} <Route Prefix>")
         | 
| 336 | 
            +
                    @prefix = parameters[0]
         | 
| 337 | 
            +
             | 
| 269 338 | 
             
                  else
         | 
| 270 339 | 
             
                    raise ConfigParser::Error.new(parser, "Unknown keyword and parameters for Interface/Router: #{keyword} #{parameters.join(" ")}")
         | 
| 271 340 |  | 
| @@ -280,12 +349,16 @@ module OpenC3 | |
| 280 349 | 
             
                  microservice_name = "#{@scope}__#{type}__#{@name}"
         | 
| 281 350 | 
             
                  microservice = MicroserviceModel.new(
         | 
| 282 351 | 
             
                    name: microservice_name,
         | 
| 283 | 
            -
                    work_dir:  | 
| 284 | 
            -
                    cmd:  | 
| 352 | 
            +
                    work_dir: @work_dir,
         | 
| 353 | 
            +
                    cmd: @cmd,
         | 
| 354 | 
            +
                    env: @env,
         | 
| 355 | 
            +
                    ports: @ports,
         | 
| 356 | 
            +
                    container: @container,
         | 
| 285 357 | 
             
                    target_names: @target_names,
         | 
| 286 358 | 
             
                    plugin: @plugin,
         | 
| 287 359 | 
             
                    needs_dependencies: @needs_dependencies,
         | 
| 288 360 | 
             
                    secrets: @secrets,
         | 
| 361 | 
            +
                    prefix: @prefix,
         | 
| 289 362 | 
             
                    scope: @scope
         | 
| 290 363 | 
             
                  )
         | 
| 291 364 | 
             
                  unless validate_only
         | 
| @@ -168,7 +168,7 @@ module OpenC3 | |
| 168 168 | 
             
                    protocol = ConfigParser.handle_nil(parameters[1])
         | 
| 169 169 | 
             
                    if protocol
         | 
| 170 170 | 
             
                      # Per https://kubernetes.io/docs/concepts/services-networking/service/#protocol-support
         | 
| 171 | 
            -
                      if %w(TCP UDP SCTP | 
| 171 | 
            +
                      if %w(TCP UDP SCTP).include?(protocol.upcase)
         | 
| 172 172 | 
             
                        @ports[-1] << protocol.upcase
         | 
| 173 173 | 
             
                      else
         | 
| 174 174 | 
             
                        raise ConfigParser::Error.new(parser, "Unknown port protocol: #{parameters[1]}", usage)
         | 
| @@ -108,9 +108,6 @@ module OpenC3 | |
| 108 108 | 
             
                  unless snooze.is_a?(Integer)
         | 
| 109 109 | 
             
                    raise ReactionInputError.new "invalid snooze value: #{snooze}"
         | 
| 110 110 | 
             
                  end
         | 
| 111 | 
            -
                  if snooze < 30
         | 
| 112 | 
            -
                    raise ReactionInputError.new "invalid snooze: '#{snooze}' must be greater than 30"
         | 
| 113 | 
            -
                  end
         | 
| 114 111 | 
             
                  return snooze
         | 
| 115 112 | 
             
                end
         | 
| 116 113 |  | 
| @@ -227,7 +227,7 @@ module OpenC3 | |
| 227 227 | 
             
                  raise "Unknown type #{type} for #{target_name} #{packet_name}" unless VALID_TYPES.include?(type)
         | 
| 228 228 |  | 
| 229 229 | 
             
                  begin
         | 
| 230 | 
            -
                    Store.hset("#{scope} | 
| 230 | 
            +
                    Store.hset("#{scope}__openc3#{type.to_s.downcase}__#{target_name}", packet_name, JSON.generate(packet.as_json(:allow_nan => true)))
         | 
| 231 231 | 
             
                  rescue JSON::GeneratorError => err
         | 
| 232 232 | 
             
                    Logger.error("Invalid text present in #{target_name} #{packet_name} #{type.to_s.downcase} packet")
         | 
| 233 233 | 
             
                    raise err
         |