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.
Files changed (123) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +5 -5
  3. data/Manifest.txt +11 -1
  4. data/README.md +3 -2
  5. data/Rakefile +18 -4
  6. data/appveyor.yml +19 -0
  7. data/cosmos.gemspec +12 -3
  8. data/data/config/cmd_tlm_server.yaml +3 -0
  9. data/data/crc.txt +63 -60
  10. data/demo/config/targets/INST/cmd_tlm_server.txt +1 -0
  11. data/demo/config/targets/INST/cmd_tlm_server2.txt +7 -0
  12. data/demo/config/tools/cmd_sequence/cmd_sequence.txt +2 -0
  13. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server.txt +8 -12
  14. data/demo/config/tools/cmd_tlm_server/cmd_tlm_server2.txt +7 -9
  15. data/demo/lib/cmd_sequence_exporter.rb +52 -0
  16. data/demo/lib/example_background_task.rb +1 -0
  17. data/demo/procedures/replay_test.rb +32 -0
  18. data/ext/cosmos/ext/structure/structure.c +39 -3
  19. data/install/config/tools/cmd_tlm_server/cmd_tlm_server.txt +1 -0
  20. data/install/config/tools/launcher/launcher.txt +2 -0
  21. data/lib/cosmos/config/config_parser.rb +2 -0
  22. data/lib/cosmos/core_ext/io.rb +89 -60
  23. data/lib/cosmos/gui/qt.rb +5 -8
  24. data/lib/cosmos/gui/qt_tool.rb +8 -8
  25. data/lib/cosmos/gui/text/ruby_editor.rb +12 -12
  26. data/lib/cosmos/gui/utilities/script_module_gui.rb +9 -9
  27. data/lib/cosmos/gui/widgets/realtime_button_bar.rb +18 -17
  28. data/lib/cosmos/interfaces/protocols/fixed_protocol.rb +2 -2
  29. data/lib/cosmos/interfaces/protocols/template_protocol.rb +3 -0
  30. data/lib/cosmos/interfaces/udp_interface.rb +27 -14
  31. data/lib/cosmos/io/buffered_file.rb +0 -1
  32. data/lib/cosmos/io/json_drb.rb +134 -214
  33. data/lib/cosmos/io/json_drb_object.rb +22 -61
  34. data/lib/cosmos/io/json_drb_rack.rb +79 -0
  35. data/lib/cosmos/io/json_rpc.rb +27 -0
  36. data/lib/cosmos/io/udp_sockets.rb +102 -58
  37. data/lib/cosmos/packets/commands.rb +1 -1
  38. data/lib/cosmos/packets/structure.rb +1 -1
  39. data/lib/cosmos/packets/structure_item.rb +37 -5
  40. data/lib/cosmos/script/cmd_tlm_server.rb +76 -2
  41. data/lib/cosmos/script/replay.rb +60 -0
  42. data/lib/cosmos/script/script.rb +20 -2
  43. data/lib/cosmos/script/scripting.rb +9 -9
  44. data/lib/cosmos/script/tools.rb +14 -0
  45. data/lib/cosmos/system/system.rb +185 -92
  46. data/lib/cosmos/system/target.rb +1 -1
  47. data/lib/cosmos/tools/cmd_sequence/cmd_sequence.rb +44 -4
  48. data/lib/cosmos/tools/cmd_sequence/sequence_item.rb +4 -0
  49. data/lib/cosmos/tools/cmd_sequence/sequence_list.rb +7 -0
  50. data/lib/cosmos/tools/cmd_tlm_server/api.rb +347 -20
  51. data/lib/cosmos/tools/cmd_tlm_server/background_tasks.rb +3 -0
  52. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server.rb +329 -111
  53. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_config.rb +13 -0
  54. data/lib/cosmos/tools/cmd_tlm_server/cmd_tlm_server_gui.rb +261 -95
  55. data/lib/cosmos/tools/cmd_tlm_server/gui/interfaces_tab.rb +46 -35
  56. data/lib/cosmos/tools/cmd_tlm_server/gui/logging_tab.rb +18 -8
  57. data/lib/cosmos/tools/cmd_tlm_server/gui/packets_tab.rb +39 -28
  58. data/lib/cosmos/tools/cmd_tlm_server/gui/replay_tab.rb +242 -0
  59. data/lib/cosmos/tools/cmd_tlm_server/gui/status_tab.rb +24 -8
  60. data/lib/cosmos/tools/cmd_tlm_server/gui/targets_tab.rb +18 -6
  61. data/lib/cosmos/tools/cmd_tlm_server/limits_groups_background_task.rb +5 -4
  62. data/lib/cosmos/tools/cmd_tlm_server/replay_backend.rb +375 -0
  63. data/lib/cosmos/tools/cmd_tlm_server/routers.rb +10 -2
  64. data/lib/cosmos/tools/data_viewer/data_viewer.rb +40 -5
  65. data/lib/cosmos/tools/handbook_creator/handbook_creator_config.rb +18 -20
  66. data/lib/cosmos/tools/launcher/launcher_config.rb +5 -16
  67. data/lib/cosmos/tools/limits_monitor/limits_monitor.rb +65 -39
  68. data/lib/cosmos/tools/packet_viewer/packet_viewer.rb +19 -0
  69. data/lib/cosmos/tools/replay/replay.rb +5 -505
  70. data/lib/cosmos/tools/script_runner/script_audit.rb +1 -0
  71. data/lib/cosmos/tools/script_runner/script_runner.rb +3 -4
  72. data/lib/cosmos/tools/script_runner/script_runner_config.rb +3 -4
  73. data/lib/cosmos/tools/script_runner/script_runner_frame.rb +44 -23
  74. data/lib/cosmos/tools/test_runner/results_writer.rb +4 -0
  75. data/lib/cosmos/tools/test_runner/test_runner.rb +0 -3
  76. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_realtime_thread.rb +6 -2
  77. data/lib/cosmos/tools/tlm_grapher/tabbed_plots_tool/tabbed_plots_tool.rb +26 -1
  78. data/lib/cosmos/tools/tlm_viewer/screen.rb +24 -1
  79. data/lib/cosmos/tools/tlm_viewer/tlm_viewer.rb +25 -0
  80. data/lib/cosmos/tools/tlm_viewer/tlm_viewer_config.rb +24 -14
  81. data/lib/cosmos/top_level.rb +34 -24
  82. data/lib/cosmos/utilities/csv.rb +60 -8
  83. data/lib/cosmos/version.rb +5 -5
  84. data/spec/config/config_parser_spec.rb +10 -1
  85. data/spec/core_ext/socket_spec.rb +4 -2
  86. data/spec/gui/utilities/script_module_gui_spec.rb +102 -0
  87. data/spec/install/config/data/data.txt +1 -0
  88. data/spec/install/config/targets/INST/cmd_tlm/inst_cmds.txt +2 -0
  89. data/spec/interfaces/cmd_tlm_server_interface_spec.rb +1 -2
  90. data/spec/interfaces/protocols/template_protocol_spec.rb +72 -2
  91. data/spec/interfaces/serial_interface_spec.rb +1 -1
  92. data/spec/interfaces/udp_interface_spec.rb +14 -0
  93. data/spec/io/buffered_file_spec.rb +37 -0
  94. data/spec/io/json_drb_object_spec.rb +2 -15
  95. data/spec/io/json_drb_spec.rb +61 -121
  96. data/spec/io/udp_sockets_spec.rb +42 -2
  97. data/spec/packet_logs/packet_log_reader_spec.rb +5 -2
  98. data/spec/packets/binary_accessor_spec.rb +1 -1
  99. data/spec/packets/packet_item_spec.rb +1 -1
  100. data/spec/packets/structure_item_spec.rb +5 -6
  101. data/spec/script/cmd_tlm_server_spec.rb +39 -4
  102. data/spec/script/commands_disconnect_spec.rb +1 -1
  103. data/spec/script/commands_spec.rb +2 -1
  104. data/spec/script/scripting_spec.rb +18 -3
  105. data/spec/script/telemetry_spec.rb +5 -0
  106. data/spec/spec_helper.rb +43 -26
  107. data/spec/streams/tcpip_socket_stream_spec.rb +2 -2
  108. data/spec/system/system_spec.rb +11 -9
  109. data/spec/system/target_spec.rb +3 -0
  110. data/spec/tools/cmd_tlm_server/api_spec.rb +543 -29
  111. data/spec/tools/cmd_tlm_server/background_task_spec.rb +2 -2
  112. data/spec/tools/cmd_tlm_server/background_tasks_spec.rb +31 -75
  113. data/spec/tools/cmd_tlm_server/cmd_tlm_server_config_spec.rb +199 -66
  114. data/spec/tools/cmd_tlm_server/cmd_tlm_server_spec.rb +85 -9
  115. data/spec/tools/cmd_tlm_server/interface_thread_spec.rb +29 -127
  116. data/spec/tools/cmd_tlm_server/router_thread_spec.rb +10 -50
  117. data/spec/tools/launcher/launcher_config_spec.rb +1 -1
  118. data/spec/tools/table_manager/table_item_spec.rb +1 -1
  119. data/spec/tools/table_manager/tablemanager_core_spec.rb +4 -4
  120. data/spec/top_level/top_level_spec.rb +151 -3
  121. data/spec/utilities/csv_spec.rb +24 -5
  122. metadata +61 -9
  123. 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
- @addr = Socket.pack_sockaddr_in(port, hostname)
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 the JSON server
61
+ # Disconnects from http server
60
62
  def disconnect
61
- Cosmos.close_socket(@socket)
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 JSON server
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 !@socket or @socket.closed? or @request_in_progress
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
- addr = Socket.pack_sockaddr_in(@port, @hostname)
116
- @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
117
- @socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
118
- @pipe_reader, @pipe_writer = IO.pipe
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 "Request:\n" if JsonDRb.debug?
122
+ STDOUT.puts "\nRequest:\n" if JsonDRb.debug?
162
123
  STDOUT.puts request_data if JsonDRb.debug?
163
124
  @request_in_progress = true
164
- JsonDRb.send_data(@socket, request_data)
165
- response_data = JsonDRb.receive_message(@socket, '', @pipe_reader)
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 "\nResponse:\n" if JsonDRb.debug?
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
- @socket = nil
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
@@ -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
- # Creates a UDPSocket and implements a non-blocking write.
22
- class UdpWriteSocket
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
- # Set source port if given
41
- @socket.bind(bind_address, src_port) if src_port
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(dest_address, dest_port)
49
+ @socket.connect(external_address, external_port) if (external_address and external_port)
45
50
 
46
51
  # Handle multicast
47
- if UdpWriteSocket.multicast?(dest_address)
48
- # Basic setup set time to live
49
- @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, ttl.to_i)
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
- # Set outgoing interface
52
- @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_IF, IPAddr.new(interface_address).hton) if interface_address
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 read.
109
- class UdpReadSocket
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
- if UdpWriteSocket.multicast?(multicast_address)
123
- interface_address = "0.0.0.0" unless interface_address
124
- membership = IPAddr.new(multicast_address).hton + IPAddr.new(interface_address).hton
125
- @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_ADD_MEMBERSHIP, membership)
126
- end
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
- # @param read_timeout [Float] Time in seconds to wait for the read to
130
- # complete
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
- # Defer all methods to the UDPSocket
147
- def method_missing(method, *args, &block)
148
- @socket.__send__(method, *args, &block)
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