cosmos 4.0.3 → 4.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 +14 -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
|
|