openc3 5.10.1 → 5.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +3 -2
- data/data/config/target.yaml +9 -0
- data/ext/openc3/ext/packet/packet.c +3 -0
- data/ext/openc3/ext/reducer_microservice/extconf.rb +13 -0
- data/ext/openc3/ext/reducer_microservice/reducer_microservice.c +165 -0
- data/ext/openc3/ext/structure/structure.c +7 -9
- data/lib/openc3/accessors/accessor.rb +53 -3
- data/lib/openc3/accessors/binary_accessor.rb +16 -0
- data/lib/openc3/accessors/cbor_accessor.rb +3 -3
- data/lib/openc3/accessors/form_accessor.rb +78 -0
- data/lib/openc3/accessors/http_accessor.rb +145 -0
- data/lib/openc3/accessors/json_accessor.rb +19 -3
- data/lib/openc3/accessors/xml_accessor.rb +18 -1
- data/lib/openc3/accessors.rb +3 -1
- data/lib/openc3/config/config_parser.rb +7 -5
- data/lib/openc3/config/meta_config_parser.rb +1 -1
- data/lib/openc3/core_ext/string.rb +16 -1
- data/lib/openc3/interfaces/http_client_interface.rb +202 -0
- data/lib/openc3/interfaces/http_server_interface.rb +183 -0
- data/lib/openc3/interfaces/interface.rb +86 -16
- data/lib/openc3/interfaces/mqtt_interface.rb +6 -5
- data/lib/openc3/interfaces/protocols/burst_protocol.rb +11 -11
- data/lib/openc3/interfaces/protocols/cobs_protocol.rb +7 -7
- data/lib/openc3/interfaces/protocols/crc_protocol.rb +7 -7
- data/lib/openc3/interfaces/protocols/length_protocol.rb +6 -6
- data/lib/openc3/interfaces/protocols/preidentified_protocol.rb +9 -5
- data/lib/openc3/interfaces/protocols/protocol.rb +8 -6
- data/lib/openc3/interfaces/protocols/slip_protocol.rb +8 -8
- data/lib/openc3/interfaces/protocols/template_protocol.rb +6 -7
- data/lib/openc3/interfaces/protocols/terminated_protocol.rb +4 -4
- data/lib/openc3/interfaces/simulated_target_interface.rb +2 -0
- data/lib/openc3/interfaces/stream_interface.rb +6 -4
- data/lib/openc3/interfaces/tcpip_server_interface.rb +2 -0
- data/lib/openc3/interfaces/udp_interface.rb +8 -5
- data/lib/openc3/interfaces.rb +2 -0
- data/lib/openc3/logs/buffered_packet_log_writer.rb +6 -7
- data/lib/openc3/logs/log_writer.rb +2 -10
- data/lib/openc3/logs/packet_log_constants.rb +13 -3
- data/lib/openc3/logs/packet_log_reader.rb +35 -98
- data/lib/openc3/logs/packet_log_writer.rb +24 -62
- data/lib/openc3/logs/text_log_writer.rb +32 -6
- data/lib/openc3/microservices/cleanup_microservice.rb +23 -16
- data/lib/openc3/microservices/decom_microservice.rb +8 -20
- data/lib/openc3/microservices/log_microservice.rb +3 -1
- data/lib/openc3/microservices/reaction_microservice.rb +22 -11
- data/lib/openc3/microservices/reducer_microservice.rb +174 -130
- data/lib/openc3/{models/notification_model.rb → microservices/scope_cleanup_microservice.rb} +20 -21
- data/lib/openc3/microservices/text_log_microservice.rb +2 -5
- data/lib/openc3/microservices/timeline_microservice.rb +0 -1
- data/lib/openc3/microservices/trigger_group_microservice.rb +0 -1
- data/lib/openc3/migrations/20230915000002_no_scope_log_messages.rb +44 -0
- data/lib/openc3/models/microservice_model.rb +1 -1
- data/lib/openc3/models/scope_model.rb +65 -34
- data/lib/openc3/models/target_model.rb +25 -5
- data/lib/openc3/models/tool_model.rb +14 -2
- data/lib/openc3/models/widget_model.rb +1 -1
- data/lib/openc3/packets/json_packet.rb +10 -2
- data/lib/openc3/packets/packet.rb +30 -9
- data/lib/openc3/packets/packet_config.rb +6 -2
- data/lib/openc3/packets/parsers/packet_item_parser.rb +11 -6
- data/lib/openc3/packets/structure.rb +19 -12
- data/lib/openc3/script/storage.rb +1 -1
- data/lib/openc3/script/web_socket_api.rb +17 -14
- data/lib/openc3/topics/telemetry_topic.rb +2 -1
- data/lib/openc3/utilities/bucket_utilities.rb +2 -0
- data/lib/openc3/utilities/cli_generator.rb +1 -1
- data/lib/openc3/utilities/logger.rb +62 -47
- data/lib/openc3/utilities/metric.rb +19 -1
- data/lib/openc3/utilities/sleeper.rb +3 -1
- data/lib/openc3/utilities/throttle.rb +76 -0
- data/lib/openc3/version.rb +6 -6
- data/templates/tool_angular/package.json +20 -20
- data/templates/tool_angular/yarn.lock +112 -106
- data/templates/tool_react/package.json +16 -18
- data/templates/tool_react/yarn.lock +977 -664
- data/templates/tool_svelte/.prettierrc.js +5 -0
- data/templates/tool_svelte/package.json +18 -18
- data/templates/tool_svelte/src/services/cable.js +1 -1
- data/templates/tool_svelte/src/services/openc3-api.js +173 -173
- data/templates/tool_svelte/yarn.lock +767 -665
- data/templates/tool_vue/package.json +10 -10
- data/templates/tool_vue/yarn.lock +225 -43
- data/templates/widget/package.json +10 -10
- data/templates/widget/yarn.lock +223 -46
- metadata +41 -4
- data/lib/openc3/topics/notifications_topic.rb +0 -31
@@ -13,7 +13,7 @@
|
|
13
13
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
#
|
16
|
-
# This file may also be used under the terms of a commercial license
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
17
|
# if purchased from OpenC3, Inc.
|
18
18
|
|
19
19
|
require 'json'
|
@@ -45,7 +45,7 @@ module OpenC3
|
|
45
45
|
buffer.replace(JSON.generate(decoded, :allow_nan => true))
|
46
46
|
end
|
47
47
|
|
48
|
-
return
|
48
|
+
return value
|
49
49
|
end
|
50
50
|
|
51
51
|
def self.read_items(items, buffer)
|
@@ -78,7 +78,7 @@ module OpenC3
|
|
78
78
|
buffer.replace(JSON.generate(decoded, :allow_nan => true))
|
79
79
|
end
|
80
80
|
|
81
|
-
return
|
81
|
+
return values
|
82
82
|
end
|
83
83
|
|
84
84
|
def self.write_item_internal(item, value, decoded)
|
@@ -130,5 +130,21 @@ module OpenC3
|
|
130
130
|
end
|
131
131
|
return decoded
|
132
132
|
end
|
133
|
+
|
134
|
+
def enforce_encoding
|
135
|
+
return nil
|
136
|
+
end
|
137
|
+
|
138
|
+
def enforce_length
|
139
|
+
return false
|
140
|
+
end
|
141
|
+
|
142
|
+
def enforce_short_buffer_allowed
|
143
|
+
return true
|
144
|
+
end
|
145
|
+
|
146
|
+
def enforce_derived_write_conversion(item)
|
147
|
+
return true
|
148
|
+
end
|
133
149
|
end
|
134
150
|
end
|
@@ -13,7 +13,7 @@
|
|
13
13
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
#
|
16
|
-
# This file may also be used under the terms of a commercial license
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
17
|
# if purchased from OpenC3, Inc.
|
18
18
|
|
19
19
|
require 'nokogiri'
|
@@ -33,6 +33,7 @@ module OpenC3
|
|
33
33
|
node = doc.xpath(item.key).first
|
34
34
|
node.content = value.to_s
|
35
35
|
buffer.replace(doc_to_buffer(doc))
|
36
|
+
return value
|
36
37
|
end
|
37
38
|
|
38
39
|
def self.read_items(items, buffer)
|
@@ -56,6 +57,7 @@ module OpenC3
|
|
56
57
|
node.content = values[index].to_s
|
57
58
|
end
|
58
59
|
buffer.replace(doc_to_buffer(doc))
|
60
|
+
return values
|
59
61
|
end
|
60
62
|
|
61
63
|
def self.buffer_to_doc(buffer)
|
@@ -66,5 +68,20 @@ module OpenC3
|
|
66
68
|
doc.to_xml
|
67
69
|
end
|
68
70
|
|
71
|
+
def enforce_encoding
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
|
75
|
+
def enforce_length
|
76
|
+
return false
|
77
|
+
end
|
78
|
+
|
79
|
+
def enforce_short_buffer_allowed
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
def enforce_derived_write_conversion(item)
|
84
|
+
return true
|
85
|
+
end
|
69
86
|
end
|
70
87
|
end
|
data/lib/openc3/accessors.rb
CHANGED
@@ -13,14 +13,16 @@
|
|
13
13
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
14
|
# GNU Affero General Public License for more details.
|
15
15
|
#
|
16
|
-
# This file may also be used under the terms of a commercial license
|
16
|
+
# This file may also be used under the terms of a commercial license
|
17
17
|
# if purchased from OpenC3, Inc.
|
18
18
|
|
19
19
|
module OpenC3
|
20
20
|
autoload(:Accessor, 'openc3/accessors/accessor.rb')
|
21
21
|
autoload(:BinaryAccessor, 'openc3/accessors/binary_accessor.rb')
|
22
22
|
autoload(:CborAccessor, 'openc3/accessors/cbor_accessor.rb')
|
23
|
+
autoload(:FormAccessor, 'openc3/accessors/form_accessor.rb')
|
23
24
|
autoload(:HtmlAccessor, 'openc3/accessors/html_accessor.rb')
|
25
|
+
autoload(:HttpAccessor, 'openc3/accessors/http_accessor.rb')
|
24
26
|
autoload(:JsonAccessor, 'openc3/accessors/json_accessor.rb')
|
25
27
|
autoload(:XmlAccessor, 'openc3/accessors/xml_accessor.rb')
|
26
28
|
end
|
@@ -166,7 +166,7 @@ module OpenC3
|
|
166
166
|
options[:locals].each { |key, value| b.local_variable_set(key, value) }
|
167
167
|
end
|
168
168
|
|
169
|
-
return ERB.new(read_file(template_name), trim_mode: "-").result(b)
|
169
|
+
return ERB.new(read_file(template_name).comment_erb(), trim_mode: "-").result(b)
|
170
170
|
end
|
171
171
|
|
172
172
|
# Can be called during parsing to read a referenced file
|
@@ -178,7 +178,7 @@ module OpenC3
|
|
178
178
|
path = File.join(File.dirname(@filename), filename)
|
179
179
|
end
|
180
180
|
OpenC3.set_working_dir(File.dirname(path)) do
|
181
|
-
return File.read(path)
|
181
|
+
return File.read(path).force_encoding("UTF-8")
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
@@ -385,10 +385,10 @@ module OpenC3
|
|
385
385
|
output = nil
|
386
386
|
if run_erb
|
387
387
|
OpenC3.set_working_dir(File.dirname(filename)) do
|
388
|
-
output = ERB.new(File.read(filename), trim_mode: "-").result(binding.set_variables(variables))
|
388
|
+
output = ERB.new(File.read(filename).force_encoding("UTF-8").comment_erb(), trim_mode: "-").result(binding.set_variables(variables))
|
389
389
|
end
|
390
390
|
else
|
391
|
-
output = File.read(filename)
|
391
|
+
output = File.read(filename).force_encoding("UTF-8")
|
392
392
|
end
|
393
393
|
rescue => e
|
394
394
|
# The first line of the backtrace indicates the line where the ERB
|
@@ -453,8 +453,10 @@ module OpenC3
|
|
453
453
|
message += "\n#{File.basename(error.filename)}:#{error.line_number}: #{error.line}"
|
454
454
|
message += "\nError: #{error.message}"
|
455
455
|
message += "\nUsage: #{error.usage}" unless error.usage.empty?
|
456
|
+
message += "\nBacktrace:"
|
457
|
+
message += "\n#{error.backtrace.join("\n")}"
|
456
458
|
else
|
457
|
-
message += "\n#{error.
|
459
|
+
message += "\n#{error.formatted}"
|
458
460
|
end
|
459
461
|
message += "\n"
|
460
462
|
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 'erb'
|
@@ -389,4 +389,19 @@ class String
|
|
389
389
|
end
|
390
390
|
end
|
391
391
|
|
392
|
-
|
392
|
+
def comment_erb
|
393
|
+
output = self.lines.collect! do |line|
|
394
|
+
# If we have a commented out line that starts with #
|
395
|
+
# but not followed by % (allows for disabling ERB comments),
|
396
|
+
# which contains an ERB statement (<% ...)
|
397
|
+
# then comment out the ERB statement (<%# ...).
|
398
|
+
# We explicitly don't comment out trailing ERB statements
|
399
|
+
# as that is not typical and is difficult to regex
|
400
|
+
if line =~ /^\s*#[^%]*<%/
|
401
|
+
line.gsub!('<%', '<%#')
|
402
|
+
end
|
403
|
+
line
|
404
|
+
end
|
405
|
+
return output.join("")
|
406
|
+
end
|
407
|
+
end
|
@@ -0,0 +1,202 @@
|
|
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/interface'
|
20
|
+
require 'openc3/config/config_parser'
|
21
|
+
require 'faraday'
|
22
|
+
require 'faraday/follow_redirects'
|
23
|
+
require 'openc3/accessors/http_accessor'
|
24
|
+
|
25
|
+
# TODO: Header Log Filtering? File Uploads? Authorization from secrets?
|
26
|
+
|
27
|
+
module OpenC3
|
28
|
+
class HttpClientInterface < Interface
|
29
|
+
# @param hostname [String] HTTP/HTTPS server to connect to
|
30
|
+
# @param port [Integer] HTTP/HTTPS port
|
31
|
+
# @param protocol [String] http or https
|
32
|
+
def initialize(hostname, port = 80, protocol = 'http', write_timeout = 5, read_timeout = nil, connect_timeout = 5, include_request_in_response = false)
|
33
|
+
super()
|
34
|
+
@hostname = hostname
|
35
|
+
@port = Integer(port)
|
36
|
+
@protocol = protocol
|
37
|
+
if (port == 80 and protocol == 'http') or (port == 443 and protocol == 'https')
|
38
|
+
@url = "#{protocol}://#{hostname}"
|
39
|
+
else
|
40
|
+
@url = "#{protocol}://#{hostname}:#{port}"
|
41
|
+
end
|
42
|
+
@write_timeout = ConfigParser.handle_nil(write_timeout)
|
43
|
+
@write_timeout = Float(@write_timeout) if @write_timeout
|
44
|
+
@read_timeout = ConfigParser.handle_nil(read_timeout)
|
45
|
+
@read_timeout = Float(@read_timeout) if @read_timeout
|
46
|
+
@connect_timeout = ConfigParser.handle_nil(connect_timeout)
|
47
|
+
@connect_timeout = Float(@connect_timeout) if @connect_timeout
|
48
|
+
@include_request_in_response = ConfigParser.handle_true_false(include_request_in_response)
|
49
|
+
|
50
|
+
@response_queue = Queue.new
|
51
|
+
end
|
52
|
+
|
53
|
+
# Connects the interface to its target(s)
|
54
|
+
def connect
|
55
|
+
# Per https://github.com/lostisland/faraday/blob/main/lib/faraday/options/env.rb
|
56
|
+
# :timeout - time limit for the entire request (Integer in seconds)
|
57
|
+
# :open_timeout - time limit for just the connection phase (e.g. handshake) (Integer in seconds)
|
58
|
+
# :read_timeout - time limit for the first response byte received from the server (Integer in seconds)
|
59
|
+
# :write_timeout - time limit for the client to send the request to the server (Integer in seconds)
|
60
|
+
request = {}
|
61
|
+
request['open_timeout'] = @connect_timeout if @connect_timeout
|
62
|
+
request['read_timeout'] = @read_timeout if @read_timeout
|
63
|
+
request['write_timeout'] = @write_timeout if @write_timeout
|
64
|
+
@http = Faraday.new(request: request) do |f|
|
65
|
+
f.response :follow_redirects # use Faraday::FollowRedirects::Middleware
|
66
|
+
f.adapter :net_http # adds the adapter to the connection, defaults to `Faraday.default_adapter`
|
67
|
+
end
|
68
|
+
super()
|
69
|
+
end
|
70
|
+
|
71
|
+
def connected?
|
72
|
+
if @http
|
73
|
+
return true
|
74
|
+
else
|
75
|
+
return false
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Disconnects the interface from its target(s)
|
80
|
+
def disconnect
|
81
|
+
@http.close if @http
|
82
|
+
@http = nil
|
83
|
+
while @response_queue.length > 0
|
84
|
+
@response_queue.pop
|
85
|
+
end
|
86
|
+
super()
|
87
|
+
@response_queue.push(nil)
|
88
|
+
end
|
89
|
+
|
90
|
+
# Reads from the socket if the read_port is defined
|
91
|
+
def read_interface
|
92
|
+
# Get the Faraday Response
|
93
|
+
data, extra = @response_queue.pop
|
94
|
+
return nil if data.nil?
|
95
|
+
|
96
|
+
read_interface_base(data, extra)
|
97
|
+
return data, extra
|
98
|
+
end
|
99
|
+
|
100
|
+
# Writes to the socket
|
101
|
+
# @param data [Hash] For the HTTP Interface, data is a hash with the needed request info
|
102
|
+
def write_interface(data, extra = nil)
|
103
|
+
extra ||= {}
|
104
|
+
queries = extra['HTTP_QUERIES']
|
105
|
+
queries ||= {}
|
106
|
+
headers = extra['HTTP_HEADERS']
|
107
|
+
headers ||= {}
|
108
|
+
uri = extra['HTTP_URI']
|
109
|
+
method = extra['HTTP_METHOD']
|
110
|
+
|
111
|
+
resp = nil
|
112
|
+
case method
|
113
|
+
when 'get'
|
114
|
+
resp = @http.get(uri, queries, headers)
|
115
|
+
when 'put'
|
116
|
+
resp = @http.put(uri) do |req|
|
117
|
+
req.params = queries
|
118
|
+
req.headers = headers
|
119
|
+
req.body = data
|
120
|
+
end
|
121
|
+
when 'delete'
|
122
|
+
resp = @http.delete(uri, queries, headers)
|
123
|
+
else # 'post'
|
124
|
+
resp = @http.post(uri) do |req|
|
125
|
+
req.params = queries
|
126
|
+
req.headers = headers
|
127
|
+
req.body = data
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Normalize Response into simple hash
|
132
|
+
response_data = nil
|
133
|
+
response_extra = {}
|
134
|
+
if resp
|
135
|
+
response_extra['HTTP_REQUEST'] = [data, extra]
|
136
|
+
if resp.headers and resp.headers.length > 0
|
137
|
+
response_extra['HTTP_HEADERS'] = resp.headers
|
138
|
+
end
|
139
|
+
response_extra['HTTP_STATUS'] = resp.status
|
140
|
+
response_data = resp.body
|
141
|
+
response_data ||= '' # Ensure an empty string if we got a response but no data
|
142
|
+
end
|
143
|
+
|
144
|
+
@response_queue.push([response_data, response_extra])
|
145
|
+
|
146
|
+
write_interface_base(data, extra)
|
147
|
+
return data, extra
|
148
|
+
end
|
149
|
+
|
150
|
+
# Called to convert the read data into a OpenC3 Packet object
|
151
|
+
#
|
152
|
+
# @param data [String] Raw packet data
|
153
|
+
# @return [Packet] OpenC3 Packet with buffer filled with data
|
154
|
+
def convert_data_to_packet(data, extra = nil)
|
155
|
+
packet = Packet.new(nil, nil, :BIG_ENDIAN, nil, data.to_s)
|
156
|
+
packet.accessor = HttpAccessor.new(packet)
|
157
|
+
if extra
|
158
|
+
# Identify the response
|
159
|
+
request_target_name = extra['HTTP_REQUEST_TARGET_NAME']
|
160
|
+
if request_target_name
|
161
|
+
request_target_name = request_target_name.to_s.upcase
|
162
|
+
response_packet_name = extra['HTTP_PACKET']
|
163
|
+
error_packet_name = extra['HTTP_ERROR_PACKET']
|
164
|
+
status = extra['HTTP_STATUS'].to_i
|
165
|
+
if status >= 300 and error_packet_name
|
166
|
+
# Handle error special case response packet
|
167
|
+
packet.target_name = request_target_name
|
168
|
+
packet.packet_name = error_packet_name.to_s.upcase
|
169
|
+
else
|
170
|
+
if response_packet_name
|
171
|
+
packet.target_name = request_target_name
|
172
|
+
packet.packet_name = response_packet_name.to_s.upcase
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if not @include_request_in_response
|
178
|
+
extra.delete("HTTP_REQUEST")
|
179
|
+
end
|
180
|
+
extra.delete("HTTP_REQUEST_TARGET_NAME")
|
181
|
+
extra.delete("HTTP_REQUEST_PACKET_NAME")
|
182
|
+
packet.extra = extra
|
183
|
+
end
|
184
|
+
|
185
|
+
return packet
|
186
|
+
end
|
187
|
+
|
188
|
+
# Called to convert a packet into the data to send
|
189
|
+
#
|
190
|
+
# @param packet [Packet] Packet to extract data from
|
191
|
+
# @return data
|
192
|
+
def convert_packet_to_data(packet)
|
193
|
+
extra = packet.extra
|
194
|
+
extra ||= {}
|
195
|
+
data = packet.buffer(true) # Copy buffer so logged command isn't modified
|
196
|
+
extra['HTTP_URI'] = URI("#{@url}#{packet.read('HTTP_PATH')}").to_s
|
197
|
+
extra['HTTP_REQUEST_TARGET_NAME'] = packet.target_name
|
198
|
+
extra['HTTP_REQUEST_PACKET_NAME'] = packet.packet_name
|
199
|
+
return data, extra
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,183 @@
|
|
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/interface'
|
20
|
+
require 'openc3/config/config_parser'
|
21
|
+
require 'openc3/accessors/http_accessor'
|
22
|
+
require 'webrick'
|
23
|
+
|
24
|
+
module OpenC3
|
25
|
+
class HttpServerInterface < Interface
|
26
|
+
# @param port [Integer] HTTP port
|
27
|
+
def initialize(port = 80)
|
28
|
+
super()
|
29
|
+
@port = Integer(port)
|
30
|
+
@server = nil
|
31
|
+
@request_queue = Queue.new
|
32
|
+
end
|
33
|
+
|
34
|
+
# Connects the interface to its target(s)
|
35
|
+
def connect
|
36
|
+
@server = WEBrick::HTTPServer.new :Port => @port
|
37
|
+
@request_queue = Queue.new
|
38
|
+
|
39
|
+
# Create a response hook for every command packet
|
40
|
+
@target_names.each do |target_name|
|
41
|
+
System.commands.packets(target_name).each do |packet_name, packet|
|
42
|
+
packet.restore_defaults
|
43
|
+
path = nil
|
44
|
+
begin
|
45
|
+
path = packet.read('HTTP_PATH')
|
46
|
+
rescue => err
|
47
|
+
# No HTTP_PATH is an error
|
48
|
+
Logger.error("HttpServerInterface Packet #{target_name} #{packet_name} unable to read HTTP_PATH\n#{err.formatted}")
|
49
|
+
end
|
50
|
+
if path
|
51
|
+
@server.mount_proc path do |req, res|
|
52
|
+
# Build the Response
|
53
|
+
begin
|
54
|
+
status = packet.read('HTTP_STATUS')
|
55
|
+
if status
|
56
|
+
res.status = status
|
57
|
+
end
|
58
|
+
rescue
|
59
|
+
# No HTTP_STATUS - Leave at default
|
60
|
+
end
|
61
|
+
|
62
|
+
if packet.extra
|
63
|
+
headers = packet.extra['HTTP_HEADERS']
|
64
|
+
if headers
|
65
|
+
headers.each do |key, value|
|
66
|
+
res[key] = value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
res.body = packet.buffer
|
72
|
+
|
73
|
+
# Save the Request
|
74
|
+
packet_name = nil
|
75
|
+
begin
|
76
|
+
packet_name = packet.read('HTTP_PACKET')
|
77
|
+
rescue
|
78
|
+
# No packet name means dont save the request as telemetry
|
79
|
+
end
|
80
|
+
if packet_name
|
81
|
+
data = req.body.to_s.dup # Dup to remove frozen
|
82
|
+
extra = {}
|
83
|
+
extra['HTTP_REQUEST_TARGET_NAME'] = target_name
|
84
|
+
extra['HTTP_REQUEST_PACKET_NAME'] = packet_name
|
85
|
+
|
86
|
+
headers = req.header
|
87
|
+
if headers
|
88
|
+
extra['HTTP_HEADERS'] = {}
|
89
|
+
headers.each do |key, value|
|
90
|
+
extra['HTTP_HEADERS'][key.downcase] = value
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
queries = req.query
|
95
|
+
if queries
|
96
|
+
extra['HTTP_QUERIES'] = {}
|
97
|
+
queries.each do |key, value|
|
98
|
+
extra['HTTP_QUERIES'][key] = value
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
@request_queue << [data, extra]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
super()
|
110
|
+
|
111
|
+
Thread.new do
|
112
|
+
# This blocks, but will be unblocked by server.shutdown called in disconnect()
|
113
|
+
@server.start
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def connected?
|
118
|
+
if @server
|
119
|
+
return true
|
120
|
+
else
|
121
|
+
return false
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Disconnects the interface from its target(s)
|
126
|
+
def disconnect
|
127
|
+
@server.shutdown if @server
|
128
|
+
@server = nil
|
129
|
+
while @request_queue.length > 0
|
130
|
+
@request_queue.pop
|
131
|
+
end
|
132
|
+
super()
|
133
|
+
@request_queue.push(nil)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Reads from the socket if the read_port is defined
|
137
|
+
def read_interface
|
138
|
+
# Get the Faraday Response
|
139
|
+
data, extra = @request_queue.pop
|
140
|
+
return nil if data.nil?
|
141
|
+
|
142
|
+
read_interface_base(data, extra)
|
143
|
+
return data, extra
|
144
|
+
end
|
145
|
+
|
146
|
+
# Writes to the socket
|
147
|
+
# @param data [Hash] For the HTTP Interface, data is a hash with the needed request info
|
148
|
+
def write_interface(data, extra = nil)
|
149
|
+
raise "Commands cannot be sent to HttpServerInterface"
|
150
|
+
end
|
151
|
+
|
152
|
+
# Called to convert the read data into a OpenC3 Packet object
|
153
|
+
#
|
154
|
+
# @param data [String] Raw packet data
|
155
|
+
# @return [Packet] OpenC3 Packet with buffer filled with data
|
156
|
+
def convert_data_to_packet(data, extra = nil)
|
157
|
+
packet = Packet.new(nil, nil, :BIG_ENDIAN, nil, data.to_s)
|
158
|
+
packet.accessor = HttpAccessor.new(packet)
|
159
|
+
if extra
|
160
|
+
# Identify the response
|
161
|
+
request_target_name = extra['HTTP_REQUEST_TARGET_NAME']
|
162
|
+
request_packet_name = extra['HTTP_REQUEST_PACKET_NAME']
|
163
|
+
if request_target_name and request_packet_name
|
164
|
+
packet.target_name = request_target_name.to_s.upcase
|
165
|
+
packet.packet_name = request_packet_name.to_s.upcase
|
166
|
+
end
|
167
|
+
extra.delete("HTTP_REQUEST_TARGET_NAME")
|
168
|
+
extra.delete("HTTP_REQUEST_PACKET_NAME")
|
169
|
+
packet.extra = extra
|
170
|
+
end
|
171
|
+
|
172
|
+
return packet
|
173
|
+
end
|
174
|
+
|
175
|
+
# Called to convert a packet into the data to send
|
176
|
+
#
|
177
|
+
# @param packet [Packet] Packet to extract data from
|
178
|
+
# @return data
|
179
|
+
def convert_packet_to_data(packet)
|
180
|
+
raise "Commands cannot be sent to HttpServerInterface"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|