cosmos 4.0.3-java → 4.1.0-java
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.
- checksums.yaml +4 -4
- data/.travis.yml +5 -5
- data/Manifest.txt +11 -1
- data/README.md +3 -2
- data/Rakefile +18 -4
- data/appveyor.yml +19 -0
- data/cosmos.gemspec +12 -3
- data/data/config/cmd_tlm_server.yaml +3 -0
- data/data/crc.txt +63 -60
- data/demo/config/targets/INST/cmd_tlm_server.txt +1 -0
- data/demo/config/targets/INST/cmd_tlm_server2.txt +7 -0
- data/demo/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
- data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +8 -12
- data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +7 -9
- data/demo/lib/cmd_sequence_exporter.rb +52 -0
- data/demo/lib/example_background_task.rb +1 -0
- data/demo/procedures/replay_test.rb +32 -0
- data/ext/cosmos/ext/structure/structure.c +39 -3
- data/install/config/tools/cmd_tlm_server/cmd_tlm_server.txt +1 -0
- data/install/config/tools/launcher/launcher.txt +2 -0
- data/lib/cosmos/config/config_parser.rb +2 -0
- data/lib/cosmos/core_ext/io.rb +89 -60
- data/lib/cosmos/gui/qt.rb +5 -8
- data/lib/cosmos/gui/qt_tool.rb +8 -8
- data/lib/cosmos/gui/text/ruby_editor.rb +12 -12
- data/lib/cosmos/gui/utilities/script_module_gui.rb +9 -9
- data/lib/cosmos/gui/widgets/realtime_button_bar.rb +18 -17
- data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +2 -2
- data/lib/cosmos/interfaces/protocols/template_protocol.rb +3 -0
- data/lib/cosmos/interfaces/udp_interface.rb +27 -14
- data/lib/cosmos/io/buffered_file.rb +0 -1
- data/lib/cosmos/io/json_drb.rb +134 -214
- data/lib/cosmos/io/json_drb_object.rb +22 -61
- data/lib/cosmos/io/json_drb_rack.rb +79 -0
- data/lib/cosmos/io/json_rpc.rb +27 -0
- data/lib/cosmos/io/udp_sockets.rb +102 -58
- data/lib/cosmos/packets/commands.rb +1 -1
- data/lib/cosmos/packets/structure.rb +1 -1
- data/lib/cosmos/packets/structure_item.rb +37 -5
- data/lib/cosmos/script/cmd_tlm_server.rb +76 -2
- data/lib/cosmos/script/replay.rb +60 -0
- data/lib/cosmos/script/script.rb +20 -2
- data/lib/cosmos/script/scripting.rb +9 -9
- data/lib/cosmos/script/tools.rb +14 -0
- data/lib/cosmos/system/system.rb +185 -92
- data/lib/cosmos/system/target.rb +1 -1
- data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +44 -4
- data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +4 -0
- data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +7 -0
- data/lib/cosmos/tools/cmd_tlm_server/api.rb +347 -20
- data/lib/cosmos/tools/cmd_tlm_server/background_tasks.rb +3 -0
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +329 -111
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +13 -0
- data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +261 -95
- data/lib/cosmos/tools/cmd_tlm_server/gui/interfaces_tab.rb +46 -35
- data/lib/cosmos/tools/cmd_tlm_server/gui/logging_tab.rb +18 -8
- data/lib/cosmos/tools/cmd_tlm_server/gui/packets_tab.rb +39 -28
- data/lib/cosmos/tools/cmd_tlm_server/gui/replay_tab.rb +242 -0
- data/lib/cosmos/tools/cmd_tlm_server/gui/status_tab.rb +24 -8
- data/lib/cosmos/tools/cmd_tlm_server/gui/targets_tab.rb +18 -6
- data/lib/cosmos/tools/cmd_tlm_server/limits_groups_background_task.rb +5 -4
- data/lib/cosmos/tools/cmd_tlm_server/replay_backend.rb +375 -0
- data/lib/cosmos/tools/cmd_tlm_server/routers.rb +10 -2
- data/lib/cosmos/tools/data_viewer/data_viewer.rb +40 -5
- data/lib/cosmos/tools/handbook_creator/handbook_creator_config.rb +18 -20
- data/lib/cosmos/tools/launcher/launcher_config.rb +5 -16
- data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +65 -39
- data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +19 -0
- data/lib/cosmos/tools/replay/replay.rb +5 -505
- data/lib/cosmos/tools/script_runner/script_audit.rb +1 -0
- data/lib/cosmos/tools/script_runner/script_runner.rb +3 -4
- data/lib/cosmos/tools/script_runner/script_runner_config.rb +3 -4
- data/lib/cosmos/tools/script_runner/script_runner_frame.rb +44 -23
- data/lib/cosmos/tools/test_runner/results_writer.rb +4 -0
- data/lib/cosmos/tools/test_runner/test_runner.rb +0 -3
- data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +6 -2
- data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_tool.rb +26 -1
- data/lib/cosmos/tools/tlm_viewer/screen.rb +24 -1
- data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +25 -0
- data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +24 -14
- data/lib/cosmos/top_level.rb +34 -24
- data/lib/cosmos/utilities/csv.rb +60 -8
- data/lib/cosmos/version.rb +5 -5
- data/spec/config/config_parser_spec.rb +10 -1
- data/spec/core_ext/socket_spec.rb +4 -2
- data/spec/gui/utilities/script_module_gui_spec.rb +102 -0
- data/spec/install/config/data/data.txt +1 -0
- data/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt +2 -0
- data/spec/interfaces/cmd_tlm_server_interface_spec.rb +1 -2
- data/spec/interfaces/protocols/template_protocol_spec.rb +72 -2
- data/spec/interfaces/serial_interface_spec.rb +1 -1
- data/spec/interfaces/udp_interface_spec.rb +14 -0
- data/spec/io/buffered_file_spec.rb +37 -0
- data/spec/io/json_drb_object_spec.rb +2 -15
- data/spec/io/json_drb_spec.rb +61 -121
- data/spec/io/udp_sockets_spec.rb +42 -2
- data/spec/packet_logs/packet_log_reader_spec.rb +5 -2
- data/spec/packets/binary_accessor_spec.rb +1 -1
- data/spec/packets/packet_item_spec.rb +1 -1
- data/spec/packets/structure_item_spec.rb +5 -6
- data/spec/script/cmd_tlm_server_spec.rb +39 -4
- data/spec/script/commands_disconnect_spec.rb +1 -1
- data/spec/script/commands_spec.rb +2 -1
- data/spec/script/scripting_spec.rb +18 -3
- data/spec/script/telemetry_spec.rb +5 -0
- data/spec/spec_helper.rb +43 -26
- data/spec/streams/tcpip_socket_stream_spec.rb +2 -2
- data/spec/system/system_spec.rb +11 -9
- data/spec/system/target_spec.rb +3 -0
- data/spec/tools/cmd_tlm_server/api_spec.rb +543 -29
- data/spec/tools/cmd_tlm_server/background_task_spec.rb +2 -2
- data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +31 -75
- data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +199 -66
- data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +85 -9
- data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +29 -127
- data/spec/tools/cmd_tlm_server/router_thread_spec.rb +10 -50
- data/spec/tools/launcher/launcher_config_spec.rb +1 -1
- data/spec/tools/table_manager/table_item_spec.rb +1 -1
- data/spec/tools/table_manager/tablemanager_core_spec.rb +4 -4
- data/spec/top_level/top_level_spec.rb +151 -3
- data/spec/utilities/csv_spec.rb +24 -5
- metadata +61 -9
- data/lib/cosmos/tools/replay/replay_server.rb +0 -91
|
@@ -15,6 +15,8 @@ require 'json'
|
|
|
15
15
|
require 'drb/acl'
|
|
16
16
|
require 'drb/drb'
|
|
17
17
|
require 'cosmos/io/json_drb'
|
|
18
|
+
require 'uri'
|
|
19
|
+
require 'httpclient'
|
|
18
20
|
|
|
19
21
|
module Cosmos
|
|
20
22
|
|
|
@@ -36,7 +38,7 @@ module Cosmos
|
|
|
36
38
|
def initialize(hostname, port, connect_timeout = 1.0)
|
|
37
39
|
hostname = '127.0.0.1' if (hostname.to_s.upcase == 'LOCALHOST')
|
|
38
40
|
begin
|
|
39
|
-
|
|
41
|
+
addr = Socket.pack_sockaddr_in(port, hostname)
|
|
40
42
|
rescue => error
|
|
41
43
|
if error.message =~ /getaddrinfo/
|
|
42
44
|
raise "Invalid hostname: #{hostname}"
|
|
@@ -46,9 +48,9 @@ module Cosmos
|
|
|
46
48
|
end
|
|
47
49
|
@hostname = hostname
|
|
48
50
|
@port = port
|
|
51
|
+
@uri = URI("http://#{@hostname}:#{@port}")
|
|
52
|
+
@http = nil
|
|
49
53
|
@mutex = Mutex.new
|
|
50
|
-
@socket = nil
|
|
51
|
-
@pipe_reader, @pipe_writer = IO.pipe
|
|
52
54
|
@id = 0
|
|
53
55
|
@request_in_progress = false
|
|
54
56
|
@connect_timeout = connect_timeout
|
|
@@ -56,17 +58,12 @@ module Cosmos
|
|
|
56
58
|
@shutdown = false
|
|
57
59
|
end
|
|
58
60
|
|
|
59
|
-
# Disconnects from
|
|
61
|
+
# Disconnects from http server
|
|
60
62
|
def disconnect
|
|
61
|
-
|
|
62
|
-
@pipe_writer.write('.')
|
|
63
|
-
# Cannot set @socket to nil here because this method can be called by
|
|
64
|
-
# other threads and @socket being nil would cause unexpected errors in method_missing
|
|
65
|
-
# Also don't want to take the mutex so that we can interrupt method_missing if necessary
|
|
66
|
-
# Only method_missing can set @socket to nil
|
|
63
|
+
@http.reset_all if @http
|
|
67
64
|
end
|
|
68
65
|
|
|
69
|
-
# Permanently disconnects from the
|
|
66
|
+
# Permanently disconnects from the http server
|
|
70
67
|
def shutdown
|
|
71
68
|
@shutdown = true
|
|
72
69
|
disconnect()
|
|
@@ -81,19 +78,14 @@ module Cosmos
|
|
|
81
78
|
# protocol a DRb::DRbConnError exception is raised.
|
|
82
79
|
def method_missing(method_name, *method_params)
|
|
83
80
|
@mutex.synchronize do
|
|
84
|
-
# This flag and loop are used to automatically reconnect and retry if something goes
|
|
85
|
-
# wrong on the first attempt writing to the socket. Sockets can become disconnected
|
|
86
|
-
# between function calls, but as long as the remote server is back up and running the
|
|
87
|
-
# call should succeed even when it discovers a broken socket on the first attempt.
|
|
88
81
|
first_try = true
|
|
89
82
|
loop do
|
|
90
83
|
raise DRb::DRbConnError, "Shutdown" if @shutdown
|
|
91
|
-
connect() if !@
|
|
84
|
+
connect() if !@http or @request_in_progress
|
|
92
85
|
|
|
93
86
|
response = make_request(method_name, method_params, first_try)
|
|
94
87
|
unless response
|
|
95
88
|
disconnect()
|
|
96
|
-
@socket = nil
|
|
97
89
|
was_first_try = first_try
|
|
98
90
|
first_try = false
|
|
99
91
|
next if was_first_try
|
|
@@ -108,44 +100,13 @@ module Cosmos
|
|
|
108
100
|
def connect
|
|
109
101
|
if @request_in_progress
|
|
110
102
|
disconnect()
|
|
111
|
-
@socket = nil
|
|
112
103
|
@request_in_progress = false
|
|
113
104
|
end
|
|
114
105
|
begin
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
begin
|
|
120
|
-
@socket.connect_nonblock(addr)
|
|
121
|
-
rescue IO::WaitWritable
|
|
122
|
-
begin
|
|
123
|
-
_, sockets, _ = IO.select(nil, [@socket], nil, @connect_timeout) # wait 3-way handshake completion
|
|
124
|
-
rescue IOError, Errno::ENOTSOCK
|
|
125
|
-
disconnect()
|
|
126
|
-
@socket = nil
|
|
127
|
-
raise "Connect canceled"
|
|
128
|
-
end
|
|
129
|
-
if sockets and !sockets.empty?
|
|
130
|
-
begin
|
|
131
|
-
@socket.connect_nonblock(addr) # check connection failure
|
|
132
|
-
rescue IOError, Errno::ENOTSOCK
|
|
133
|
-
disconnect()
|
|
134
|
-
@socket = nil
|
|
135
|
-
raise "Connect canceled"
|
|
136
|
-
rescue Errno::EINPROGRESS
|
|
137
|
-
retry
|
|
138
|
-
rescue Errno::EISCONN, Errno::EALREADY
|
|
139
|
-
end
|
|
140
|
-
else
|
|
141
|
-
disconnect()
|
|
142
|
-
@socket = nil
|
|
143
|
-
raise "Connect timeout"
|
|
144
|
-
end
|
|
145
|
-
rescue IOError, Errno::ENOTSOCK
|
|
146
|
-
disconnect()
|
|
147
|
-
@socket = nil
|
|
148
|
-
raise "Connect canceled"
|
|
106
|
+
if !@http
|
|
107
|
+
@http = HTTPClient.new
|
|
108
|
+
@http.connect_timeout = @connect_timeout
|
|
109
|
+
@http.receive_timeout = nil # Allow long polling
|
|
149
110
|
end
|
|
150
111
|
rescue => e
|
|
151
112
|
raise DRb::DRbConnError, e.message
|
|
@@ -158,17 +119,19 @@ module Cosmos
|
|
|
158
119
|
|
|
159
120
|
request_data = request.to_json(:allow_nan => true)
|
|
160
121
|
begin
|
|
161
|
-
STDOUT.puts "
|
|
122
|
+
STDOUT.puts "\nRequest:\n" if JsonDRb.debug?
|
|
162
123
|
STDOUT.puts request_data if JsonDRb.debug?
|
|
163
124
|
@request_in_progress = true
|
|
164
|
-
|
|
165
|
-
|
|
125
|
+
headers = {'Content-Type' => 'application/json-rpc'}
|
|
126
|
+
res = @http.post(@uri,
|
|
127
|
+
:body => request_data,
|
|
128
|
+
:header => headers)
|
|
129
|
+
response_data = res.body
|
|
166
130
|
@request_in_progress = false
|
|
167
|
-
STDOUT.puts "
|
|
131
|
+
STDOUT.puts "Response:\n" if JsonDRb.debug?
|
|
168
132
|
STDOUT.puts response_data if JsonDRb.debug?
|
|
169
133
|
rescue => e
|
|
170
134
|
disconnect()
|
|
171
|
-
@socket = nil
|
|
172
135
|
return false if first_try
|
|
173
136
|
raise DRb::DRbConnError, e.message, e.backtrace
|
|
174
137
|
end
|
|
@@ -177,7 +140,7 @@ module Cosmos
|
|
|
177
140
|
|
|
178
141
|
def handle_response(response_data)
|
|
179
142
|
# The code below will always either raise or return breaking out of the loop
|
|
180
|
-
if response_data
|
|
143
|
+
if response_data and response_data.to_s.length > 0
|
|
181
144
|
response = JsonRpcResponse.from_json(response_data)
|
|
182
145
|
if JsonRpcErrorResponse === response
|
|
183
146
|
if response.error.data
|
|
@@ -189,10 +152,8 @@ module Cosmos
|
|
|
189
152
|
return response.result
|
|
190
153
|
end
|
|
191
154
|
else
|
|
192
|
-
# Socket was closed by server
|
|
193
155
|
disconnect()
|
|
194
|
-
|
|
195
|
-
raise DRb::DRbConnError, "Socket closed by server"
|
|
156
|
+
raise DRb::DRbConnError, "No response from server"
|
|
196
157
|
end
|
|
197
158
|
end
|
|
198
159
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
# Copyright 2017 Ball Aerospace & Technologies Corp.
|
|
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 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
|
+
require 'rack'
|
|
12
|
+
|
|
13
|
+
module Cosmos
|
|
14
|
+
|
|
15
|
+
# JsonDrbRack implements a rack application that can be served by a
|
|
16
|
+
# webserver to process COSMOS json_drb requests via http.
|
|
17
|
+
class JsonDrbRack
|
|
18
|
+
# @param drb [JsonDRb] - An instance of the JsonDRb class that'll be used
|
|
19
|
+
# to process the JSON request and generate a response
|
|
20
|
+
def initialize(drb)
|
|
21
|
+
@drb = drb
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Handles a request.
|
|
25
|
+
#
|
|
26
|
+
# @param env [Hash] - A rack env hash, can be turned into a Rack::Request
|
|
27
|
+
# @return [Integer, Hash, [String]] - Http response code, content headers,
|
|
28
|
+
# response body
|
|
29
|
+
def call(env)
|
|
30
|
+
request = Rack::Request.new(env)
|
|
31
|
+
|
|
32
|
+
# ACL allow_addr? function takes address in the form returned by
|
|
33
|
+
# IPSocket.peeraddr.
|
|
34
|
+
req_addr = ["AF_INET", request.port, request.host.to_s, request.ip.to_s]
|
|
35
|
+
|
|
36
|
+
if @drb.acl and !@drb.acl.allow_addr?(req_addr)
|
|
37
|
+
status = 403
|
|
38
|
+
content_type = "text/plain"
|
|
39
|
+
body = "Forbidden"
|
|
40
|
+
elsif request.post?
|
|
41
|
+
status, content_type, body = handle_post(request)
|
|
42
|
+
else
|
|
43
|
+
status = 405
|
|
44
|
+
content_type = "text/plain"
|
|
45
|
+
body = "Request not allowed"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return status, {'Content-Type' => content_type}, [body]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Handles an http post.
|
|
52
|
+
#
|
|
53
|
+
# @param request [Rack::Request] - A rack post request
|
|
54
|
+
# @return [Integer, String, String] - Http response code, content type,
|
|
55
|
+
# response body.
|
|
56
|
+
def handle_post(request)
|
|
57
|
+
request_data = request.body.read
|
|
58
|
+
start_time = Time.now.sys
|
|
59
|
+
response_data, error_code = @drb.process_request(request_data, start_time)
|
|
60
|
+
|
|
61
|
+
# Convert json error code into html status code
|
|
62
|
+
# see http://www.jsonrpc.org/historical/json-rpc-over-http.html#errors
|
|
63
|
+
if error_code
|
|
64
|
+
case error_code
|
|
65
|
+
when JsonRpcError::ErrorCode::PARSE_ERROR then status = 500 # Internal server error
|
|
66
|
+
when JsonRpcError::ErrorCode::INVALID_REQUEST then status = 400 # Bad request
|
|
67
|
+
when JsonRpcError::ErrorCode::METHOD_NOT_FOUND then status = 404 # Not found
|
|
68
|
+
when JsonRpcError::ErrorCode::INVALID_PARAMS then status = 500 # Internal server error
|
|
69
|
+
when JsonRpcError::ErrorCode::INTERNAL_ERROR then status = 500 # Internal server error
|
|
70
|
+
else status = 500 # Internal server error
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
status = 200 # OK
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
return status, "application/json-rpc", response_data
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/cosmos/io/json_rpc.rb
CHANGED
|
@@ -60,6 +60,23 @@ class Numeric
|
|
|
60
60
|
def as_json(options = nil) self end #:nodoc:
|
|
61
61
|
end
|
|
62
62
|
|
|
63
|
+
class Float
|
|
64
|
+
def self.json_create(object)
|
|
65
|
+
case object['raw']
|
|
66
|
+
when "Infinity" then Float::INFINITY
|
|
67
|
+
when "-Infinity" then -Float::INFINITY
|
|
68
|
+
when "NaN" then Float::NAN
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def as_json(options = nil)
|
|
73
|
+
return {"json_class"=>Float, "raw"=>"Infinity"} if self.infinite? == 1
|
|
74
|
+
return {"json_class"=>Float, "raw"=>"-Infinity"} if self.infinite? == -1
|
|
75
|
+
return {"json_class"=>Float, "raw"=>"NaN"} if self.nan?
|
|
76
|
+
return self
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
63
80
|
class Regexp
|
|
64
81
|
def as_json(options = nil) to_s end #:nodoc:
|
|
65
82
|
end
|
|
@@ -324,6 +341,16 @@ module Cosmos
|
|
|
324
341
|
# Represents a JSON Remote Procedure Call Error
|
|
325
342
|
class JsonRpcError < JsonRpc
|
|
326
343
|
|
|
344
|
+
# Enumeration of JSON RPC error codes
|
|
345
|
+
class ErrorCode
|
|
346
|
+
PARSE_ERROR = -32700
|
|
347
|
+
INVALID_REQUEST = -32600
|
|
348
|
+
METHOD_NOT_FOUND = -32601
|
|
349
|
+
INVALID_PARAMS = -32602
|
|
350
|
+
INTERNAL_ERROR = -32603
|
|
351
|
+
OTHER_ERROR = -1
|
|
352
|
+
end
|
|
353
|
+
|
|
327
354
|
# @param code [Integer] The error type that occurred
|
|
328
355
|
# @param message [String] A short description of the error
|
|
329
356
|
# @param data [Hash] Additional information about the error
|
|
@@ -18,38 +18,55 @@ Socket::IP_MULTICAST_TTL = 10 unless Socket.const_defined?('IP_MULTICAST_TTL')
|
|
|
18
18
|
|
|
19
19
|
module Cosmos
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
class UdpReadWriteSocket
|
|
22
|
+
# @param bind_port [Integer[ Port to write data out from and receive data on (0 = randomly assigned)
|
|
23
|
+
# @param bind_address [String] Local address to bind to (0.0.0.0 = All local addresses)
|
|
24
|
+
# @param external_port [Integer] External port to write to
|
|
25
|
+
# @param external_address [String] External host to send data to
|
|
26
|
+
# @param multicast_interface_address [String] Local outgoing interface to send multicast packets from
|
|
27
|
+
# @param ttl [Integer] Time To Live for outgoing multicast packets
|
|
28
|
+
# @param read_multicast [Boolean] Whether or not to try to read from the external address as multicast
|
|
29
|
+
# @param write_multicast [Boolean] Whether or not to write to the external address as multicast
|
|
30
|
+
def initialize(
|
|
31
|
+
bind_port = 0,
|
|
32
|
+
bind_address = "0.0.0.0",
|
|
33
|
+
external_port = nil,
|
|
34
|
+
external_address = nil,
|
|
35
|
+
multicast_interface_address = nil,
|
|
36
|
+
ttl = 1,
|
|
37
|
+
read_multicast = true,
|
|
38
|
+
write_multicast = true)
|
|
23
39
|
|
|
24
|
-
# @param dest_address [String] Host to send data to
|
|
25
|
-
# @param dest_port [Integer] Port to send data to
|
|
26
|
-
# @param src_port [Integer[ Port to send data out from
|
|
27
|
-
# @param interface_address [String] Local outgoing interface to send from
|
|
28
|
-
# @param ttl [Integer] Time To Live for outgoing packets
|
|
29
|
-
def initialize(dest_address,
|
|
30
|
-
dest_port,
|
|
31
|
-
src_port = nil,
|
|
32
|
-
interface_address = nil,
|
|
33
|
-
ttl = 1,
|
|
34
|
-
bind_address = "0.0.0.0")
|
|
35
40
|
@socket = UDPSocket.new
|
|
36
41
|
|
|
37
42
|
# Basic setup to reuse address
|
|
38
43
|
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
|
|
39
44
|
|
|
40
|
-
#
|
|
41
|
-
@socket.bind(bind_address,
|
|
45
|
+
# Bind to local address and port - This sets recv port, write_src port, recv_address, and write_src_address
|
|
46
|
+
@socket.bind(bind_address, bind_port) if (bind_address and bind_port)
|
|
42
47
|
|
|
43
48
|
# Default send to the specified address and port
|
|
44
|
-
@socket.connect(
|
|
49
|
+
@socket.connect(external_address, external_port) if (external_address and external_port)
|
|
45
50
|
|
|
46
51
|
# Handle multicast
|
|
47
|
-
if
|
|
48
|
-
|
|
49
|
-
|
|
52
|
+
if UdpReadWriteSocket.multicast?(external_address)
|
|
53
|
+
if write_multicast
|
|
54
|
+
# Basic setup set time to live
|
|
55
|
+
@socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, ttl.to_i)
|
|
56
|
+
|
|
57
|
+
# Set outgoing interface
|
|
58
|
+
@socket.setsockopt(
|
|
59
|
+
Socket::IPPROTO_IP,
|
|
60
|
+
Socket::IP_MULTICAST_IF,
|
|
61
|
+
IPAddr.new(multicast_interface_address).hton) if multicast_interface_address
|
|
62
|
+
end
|
|
50
63
|
|
|
51
|
-
#
|
|
52
|
-
|
|
64
|
+
# Receive messages sent to the multicast address
|
|
65
|
+
if read_multicast
|
|
66
|
+
multicast_interface_address = "0.0.0.0" unless multicast_interface_address
|
|
67
|
+
membership = IPAddr.new(external_address).hton + IPAddr.new(multicast_interface_address).hton
|
|
68
|
+
@socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, membership)
|
|
69
|
+
end
|
|
53
70
|
end
|
|
54
71
|
end
|
|
55
72
|
|
|
@@ -78,6 +95,23 @@ module Cosmos
|
|
|
78
95
|
end
|
|
79
96
|
end
|
|
80
97
|
|
|
98
|
+
# @param read_timeout [Float] Time in seconds to wait for the read to
|
|
99
|
+
# complete
|
|
100
|
+
def read(read_timeout = nil)
|
|
101
|
+
data = nil
|
|
102
|
+
begin
|
|
103
|
+
data, _ = @socket.recvfrom_nonblock(65536)
|
|
104
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
105
|
+
result = IO.fast_select([@socket], nil, nil, read_timeout)
|
|
106
|
+
if result
|
|
107
|
+
retry
|
|
108
|
+
else
|
|
109
|
+
raise Timeout::Error, "Read Timeout"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
data
|
|
113
|
+
end
|
|
114
|
+
|
|
81
115
|
# Defer all methods to the UDPSocket
|
|
82
116
|
def method_missing(method, *args, &block)
|
|
83
117
|
@socket.__send__(method, *args, &block)
|
|
@@ -105,47 +139,57 @@ module Cosmos
|
|
|
105
139
|
end
|
|
106
140
|
end
|
|
107
141
|
|
|
108
|
-
# Creates a UDPSocket and implements a non-blocking
|
|
109
|
-
class
|
|
110
|
-
|
|
111
|
-
# @param recv_port [Integer] Port to receive data on
|
|
112
|
-
# @param multicast_address [String] Address to add multicast
|
|
113
|
-
def initialize(recv_port = 0, multicast_address = nil, interface_address = nil, bind_address = "0.0.0.0")
|
|
114
|
-
@socket = UDPSocket.new
|
|
115
|
-
|
|
116
|
-
# Basic setup to reuse address
|
|
117
|
-
@socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1)
|
|
118
|
-
|
|
119
|
-
# bind to port
|
|
120
|
-
@socket.bind(bind_address, recv_port)
|
|
142
|
+
# Creates a UDPSocket and implements a non-blocking write.
|
|
143
|
+
class UdpWriteSocket < UdpReadWriteSocket
|
|
121
144
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
145
|
+
# @param dest_address [String] Host to send data to
|
|
146
|
+
# @param dest_port [Integer] Port to send data to
|
|
147
|
+
# @param src_port [Integer[ Port to send data out from
|
|
148
|
+
# @param multicast_interface_address [String] Local outgoing interface to send multicast packets from
|
|
149
|
+
# @param ttl [Integer] Time To Live for outgoing packets
|
|
150
|
+
# @param bind_address [String] Local address to bind to (0.0.0.0 = All local addresses)
|
|
151
|
+
def initialize(
|
|
152
|
+
dest_address,
|
|
153
|
+
dest_port,
|
|
154
|
+
src_port = nil,
|
|
155
|
+
multicast_interface_address = nil,
|
|
156
|
+
ttl = 1,
|
|
157
|
+
bind_address = "0.0.0.0")
|
|
158
|
+
|
|
159
|
+
super(
|
|
160
|
+
src_port,
|
|
161
|
+
bind_address,
|
|
162
|
+
dest_port,
|
|
163
|
+
dest_address,
|
|
164
|
+
multicast_interface_address,
|
|
165
|
+
ttl,
|
|
166
|
+
false,
|
|
167
|
+
true)
|
|
127
168
|
end
|
|
169
|
+
end
|
|
128
170
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def read(read_timeout = nil)
|
|
132
|
-
data = nil
|
|
133
|
-
begin
|
|
134
|
-
data, _ = @socket.recvfrom_nonblock(65536)
|
|
135
|
-
rescue Errno::EAGAIN, Errno::EWOULDBLOCK
|
|
136
|
-
result = IO.fast_select([@socket], nil, nil, read_timeout)
|
|
137
|
-
if result
|
|
138
|
-
retry
|
|
139
|
-
else
|
|
140
|
-
raise Timeout::Error, "Read Timeout"
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
data
|
|
144
|
-
end
|
|
171
|
+
# Creates a UDPSocket and implements a non-blocking read.
|
|
172
|
+
class UdpReadSocket < UdpReadWriteSocket
|
|
145
173
|
|
|
146
|
-
#
|
|
147
|
-
|
|
148
|
-
|
|
174
|
+
# @param recv_port [Integer] Port to receive data on
|
|
175
|
+
# @param multicast_address [String] Address to add multicast
|
|
176
|
+
# @param multicast_interface_address [String] Local incoming interface to receive multicast packets on
|
|
177
|
+
# @param bind_address [String] Local address to bind to (0.0.0.0 = All local addresses)
|
|
178
|
+
def initialize(
|
|
179
|
+
recv_port = 0,
|
|
180
|
+
multicast_address = nil,
|
|
181
|
+
multicast_interface_address = nil,
|
|
182
|
+
bind_address = "0.0.0.0")
|
|
183
|
+
|
|
184
|
+
super(
|
|
185
|
+
recv_port,
|
|
186
|
+
bind_address,
|
|
187
|
+
nil,
|
|
188
|
+
multicast_address,
|
|
189
|
+
multicast_interface_address,
|
|
190
|
+
1,
|
|
191
|
+
true,
|
|
192
|
+
false)
|
|
149
193
|
end
|
|
150
194
|
end
|
|
151
195
|
|