sping 1.0.3 → 1.1.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 74faa9103509651e47d2f098c22b66fae80ca1ba0b71b132aa636d26bf1e0f04
4
- data.tar.gz: 9ae6b44c52ded9e1999eb3fe467216b6d640747d11a8b69f4eed864efeb30a03
3
+ metadata.gz: 7dc97d43dd1c7805574269a6ebbe8113c49c431bdfa46783b8e43ebef14063a5
4
+ data.tar.gz: ffb03def610179c609ffd4046cd5a7da5659e73b83ae867a123a2fcf62309b72
5
5
  SHA512:
6
- metadata.gz: c08fcd7358309abe6a7f67044bbf267f31cba483786dc387b78ef14e8ce32801b067bd2ca8e2a545e141d3ffe843d004a4427d1a91cba1cbdd8d154679344653
7
- data.tar.gz: 56ea68be301c25d116657775aabf26a9fb9fdf41cf2224ff9dcd72be1b897f20b076e9d0f4a1154ec777c53f20eeff173ded69985c993c99169198290f66a092
6
+ metadata.gz: 637dc51c01d6f89bb26a6175d3ac0f0a16921398db3244164efea4b1de457cd3ef1329f3b4ee6eed69a1a57961a6c0d64c31fab7f4fb1fed150183212d830b96
7
+ data.tar.gz: 5bc1661058eb425277ed0d37429b5b0e8376690a6665aeb85e8bda35b96b85fd3a4025f0b4cc2020aef7b9e1f847b24845b6db9e841705749a7db3a20e4e9ee3
data/README.md CHANGED
@@ -1,3 +1,5 @@
1
1
  # sping
2
2
 
3
- This is a reimplementation of [sping](https://github.com/benjojo/sping/) in Ruby.
3
+ Program for measuring asymmetric latencies.
4
+
5
+ This is a reimplementation in Ruby of the reference implementation of the sping protocol in Go. The program provides both the client and server part to measure asymmetric latencies between two peers.
data/lib/errors.rb ADDED
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ # sharable_constant_value: literal
3
+
4
+ # SPing / Splitted Ping is a protocol for measuring asymmetric latencies.
5
+ module SPing
6
+ # Generic SPing error.
7
+ class SPingError < StandardError; end
8
+
9
+ # Error that occurred while managing the sessions.
10
+ class SessionManagerError < SPingError; end
11
+
12
+ # Error that is thrown when there are not enough session IDs available to establish a
13
+ # connection to a peer or to accept new ones.
14
+ # This indicates either a programmer error or an overload of the server.
15
+ class OutOfSessions < SessionManagerError; end
16
+
17
+ # Error that occurs in connection with a session.
18
+ class SessionError < SPingError; end
19
+
20
+ # Error during the TCP handshake
21
+ class TCPHandshakeError < SessionError; end
22
+
23
+ # Error due to the peer (i.e. not self-inflicted).
24
+ class PeerError < SPingError; end
25
+
26
+ # Package was not coded correctly.
27
+ class InvalidPacketError < PeerError; end
28
+
29
+ # The package was coded correctly, but the content is invalid.
30
+ class InvalidMessageError < InvalidPacketError; end
31
+
32
+ # A packet has been received which cannot be clearly assigned to SPing
33
+ # and therefore cannot be processed further.
34
+ class UnknownPacketError < InvalidPacketError; end
35
+
36
+ # The peer has signaled an error.
37
+ class SignalizedError < PeerError; end
38
+
39
+ # The peer uses a version of sping that is not supported.
40
+ class UnsupportedVersionError < PeerError; end
41
+
42
+ # The package could not be assigned to a current session.
43
+ class UnknownSessionError < PeerError; end
44
+ end
data/lib/last_acks.rb CHANGED
@@ -1,34 +1,43 @@
1
1
  # frozen_string_literal: true
2
2
  # sharable_constant_value: literal
3
3
 
4
- class LastAcks
5
- attr_reader :size
4
+ # SPing / Splitted Ping is a protocol for measuring asymmetric latencies.
5
+ module SPing
6
+ # A kind of circular buffer, which consists of 32 acks at any given time. If no acks are available,
7
+ # empty ones are created. If a new ack is added (which would result in 33 acks), one is removed.
8
+ class LastAcks
9
+ # Creates a new circular buffer for acks
10
+ def initialize
11
+ # Array in which the acks are stored.
12
+ @acks = []
13
+ # Mutex, which ensures that the array is not accessed simultaneously.
14
+ @acks_mutex = Mutex.new
6
15
 
7
- def initialize(size = 32)
8
- @size = size
9
- @acks = []
10
- @acks_mutex = Mutex.new
11
-
12
- @size.times do |index|
13
- @acks[index] = {
14
- 'R' => 0,
15
- 'U' => Time.at(0),
16
- 'X' => Time.at(0)
17
- }
16
+ # Initiallize the circular buffer with 32 empty acks.
17
+ 32.times do |index|
18
+ @acks[index] = {
19
+ 'R' => 0,
20
+ 'U' => Time.at(0),
21
+ 'X' => Time.at(0)
22
+ }
23
+ end
18
24
  end
19
- end
20
25
 
21
- def acks
22
- @acks_mutex.synchronize do
23
- return @acks.dup
26
+ # Returns a copy of the last 32 acks.
27
+ # @return [Array] Last 32 Acks
28
+ def acks
29
+ @acks_mutex.synchronize do
30
+ return @acks.dup
31
+ end
24
32
  end
25
- end
26
-
27
- def add_ack(ack)
28
- @acks_mutex.synchronize do
29
- @acks << ack
30
33
 
31
- @acks.shift if @acks.length > @size
34
+ # Adds a new ack and deletes the oldest one
35
+ # @param ack [Hash] New ack to be added
36
+ def add_ack(ack)
37
+ @acks_mutex.synchronize do
38
+ @acks << ack
39
+ @acks.shift
40
+ end
32
41
  end
33
42
  end
34
43
  end
data/lib/session.rb CHANGED
@@ -1,69 +1,118 @@
1
1
  # frozen_string_literal: true
2
2
  # sharable_constant_value: literal
3
3
 
4
+ # SPing / Splitted Ping is a protocol for measuring asymmetric latencies.
4
5
  module SPing
5
- class SessionError < StandardError; end
6
- class TCPHandshakeError < SessionError; end
6
+ # Inclusion of all possible SPing-specific errors.
7
+ require_relative 'errors'
7
8
 
9
+ # Models or represents a session with a peer.
8
10
  class Session
9
- attr_reader :created, :last_rx, :madebyme
10
- attr_accessor :session_id, :tcp_handshake_complete, :udp_handshake_complete
11
+ # Time when the session was created. This is necessary so that the session GC knows when a
12
+ # non-initialized session can be deleted.
13
+ # @return [Time]
14
+ attr_reader :created
15
+ # Time when the last ping packet was received by the peer. This is important so that the session
16
+ # GC knows when a session is considered inactive and can be deleted.
17
+ # @return [Time]
18
+ attr_reader :last_rx
19
+ # Indicates whether the session has been initialized. true if we have initiated it.
20
+ # false if the peer has initiated it.
21
+ # @return [TrueClass, FalseClass]
22
+ attr_reader :madebyme
23
+ # Indicates whether a TCP handshake has been carried out (successfully).
24
+ # @return [TrueClass, FalseClass]
25
+ attr_reader :tcp_handshake_complete
26
+ # Indicates whether a UDP handshake has been carried out (successfully).
27
+ # @return [TrueClass, FalseClass]
28
+ attr_reader :udp_handshake_complete
29
+ # The session ID in the range from 1 to 2**32 - 1.
30
+ # @return [Integer]
31
+ attr_reader :session_id
11
32
 
12
33
  require 'socket'
13
34
  require 'timeout'
14
35
  require 'msgpack'
15
36
  require_relative 'last_acks'
16
37
 
38
+ # Creates a new session
39
+ # @param host [#to_s] Host of the peer
40
+ # @param port [#to_i] Port of the peer
41
+ # @param socket [UDPSocket] UDP socket via which the pings and the UDP handshake are to be sent.
42
+ # @param madebyme [TrueClass, FalseClass] Indicates whether we have initiated the session.
17
43
  def initialize(host, port, socket, madebyme)
18
- @host = host
19
- @port = port
44
+ # Assign the passed parameters to instance variables.
45
+ @host = host.to_s
46
+ @port = port.to_i
20
47
  @socket = socket
21
- @created = Time.now
22
48
  @madebyme = madebyme
23
49
 
50
+ # Initialize assignment of other instance variables
51
+ @created = Time.now
52
+
24
53
  @tcp_handshake_complete = false
25
54
  @udp_handshake_complete = false
26
55
 
27
- @last_acks = LastAcks.new
56
+ @last_acks = SPing::LastAcks.new
28
57
  end
29
58
 
59
+ # Initiates a TCP handshake.
60
+ # @param socket [TCPSocket] Socket via which the handshake is to be sent
61
+ # (and responses are to be expected).
62
+ # @param session_id [Integer] Session ID which is to be sent to the peer
63
+ # and which is to be used for the current session.
30
64
  def do_tcp_handshake1(socket, session_id)
65
+ # Send the banner
31
66
  socket.write "sping-0.3-https://codeberg.org/mark22k/sping\r\n"
67
+ # and make sure that it is no longer in the send buffer.
32
68
  socket.flush
33
69
 
70
+ # See if the peer invites us to create a session with them.
34
71
  invite = socket.readpartial 6
35
-
36
72
  raise TCPHandshakeError, 'Peer didn\'t invite us.' unless invite.chomp == 'INVITE'
37
73
 
74
+ # Send the session ID
38
75
  @session_id = session_id
39
-
40
76
  socket.write "#{session_id}\r\n"
77
+ # and make sure that it is no longer in the send buffer.
41
78
  socket.flush
42
79
 
80
+ # This means that the TCP handshake is successful. The socket can be closed
81
+ # and the corresponding instance variable can be set.
43
82
  socket.close
44
83
 
45
84
  @tcp_handshake_complete = true
46
85
  end
47
86
 
87
+ # Receives a TCP handshake from a peer.
88
+ # The peer specified when the session is created is consulted for this purpose.
48
89
  def do_tcp_handshake2
90
+ # Establish a connection and receive the banner.
49
91
  socket = TCPSocket.new @host, @port
92
+
93
+ # If the banner is too large, close the socket and throw an error.
94
+ # The handshake was not successful.
50
95
  banner = socket.readpartial 9001
51
96
  if banner.length > 9000
52
97
  socket.close
53
98
  raise TCPHandshakeError, 'Host banner too big'
54
99
  end
55
100
 
101
+ # If the banner does not match the SPing service, close the socket and
102
+ # throw an error. The handshake was not successful.
56
103
  unless banner.start_with? 'sping-0.3-'
57
104
  socket.close
58
- raise TCPHandshakeError, 'Host banner not sping'
105
+ raise TCPHandshakeError, 'Host banner not sping or unsupported version of sping.'
59
106
  end
60
107
 
61
108
  @remote_version = banner.chomp
62
109
  $logger.info "Peer uses the following program version: #{@remote_version.dump}"
63
110
 
111
+ # Invite the peer to start a session with us.
64
112
  socket.write "INVITE\r\n"
65
113
  socket.flush
66
114
 
115
+ # If the session ID is too long or none was received, close the socket and throw an error.
67
116
  invite_buf = socket.readpartial 32
68
117
  if invite_buf.length > 31 || invite_buf.empty?
69
118
  socket.close
@@ -77,15 +126,21 @@ module SPing
77
126
  @tcp_handshake_complete = true
78
127
  end
79
128
 
129
+ # Sets the endpoint consisting of the host and port of the remote end. This is done
130
+ # each time a new packet is received and ensures that the current endpoint is always available.
131
+ # @param host [#to_s]
132
+ # @param port [#to_i]
80
133
  def set_endpoint(host, port)
81
- @host = host
82
- @port = port
134
+ @host = host.to_s
135
+ @port = port.to_i
83
136
  end
84
137
 
138
+ # Starts a thread which sends the UDP handshake at regular intervals.
139
+ # @param send_interval [#to_i] The interval at which the UDP handshakes are to be sent.
85
140
  def start_udp_handshake_sender(send_interval = 5)
86
141
  raise 'UDP handshake sender is already running.' if @udp_handshake_sender
87
142
 
88
- @udp_handshake_sender = Thread.new(send_interval) do |th_send_interval|
143
+ @udp_handshake_sender = Thread.new(send_interval.to_i) do |th_send_interval|
89
144
  loop do
90
145
  send_udp_handshake
91
146
  sleep th_send_interval
@@ -93,6 +148,7 @@ module SPing
93
148
  end
94
149
  end
95
150
 
151
+ # Send a single UDP handshake
96
152
  def send_udp_handshake
97
153
  packet = {
98
154
  'Y' => 'h'.ord,
@@ -105,6 +161,7 @@ module SPing
105
161
  @socket.send packet, 0, @host, @port
106
162
  end
107
163
 
164
+ # Stop the UDP handshake sender. UDP handshakes are then no longer sent at any interval.
108
165
  def stop_udp_handshake_sender
109
166
  raise 'UDP handshake sender is not running and therefore cannot be terminated.' unless @udp_handshake_sender
110
167
 
@@ -112,11 +169,13 @@ module SPing
112
169
  @udp_handshake_sender = nil
113
170
  end
114
171
 
172
+ # Informs the session that a UDP handshake has been received.
115
173
  def udp_handshake_recived
116
174
  stop_udp_handshake_sender if @madebyme
117
175
  @udp_handshake_complete = true
118
176
  end
119
177
 
178
+ # Starts a thread which sends ping or time messages to the peer at one-second intervals.
120
179
  def start_pinger
121
180
  raise 'Pinger is already running.' if @pinger
122
181
 
@@ -128,6 +187,7 @@ module SPing
128
187
  end
129
188
  end
130
189
 
190
+ # Stops the thread, which sends ping or time messages to the peer at regular intervals.
131
191
  def stop_pinger
132
192
  raise 'Pinger sender is not running and therefore cannot be terminated.' unless @pinger
133
193
 
@@ -135,6 +195,7 @@ module SPing
135
195
  @pinger = nil
136
196
  end
137
197
 
198
+ # Sends a ping message to the peer.
138
199
  def ping
139
200
  current_id = (Time.now.to_i % 255) + 1
140
201
  data = {
@@ -151,21 +212,45 @@ module SPing
151
212
  @socket.send data, 0, @host, @port
152
213
  end
153
214
 
215
+ # Stops all threads associated with the session. This means that the session to the peer is as good as dead.
154
216
  def stop
155
217
  @pinger&.kill
156
218
  @udp_handshake_sender&.kill
157
219
  end
158
220
 
221
+ # Handler that receives and processes a receiving ping packet or time message. The current statistics are output.
222
+ # @param packet [Hash]
223
+ # @param rxtime [Time]
224
+ # @param peeraddr [#to_s]
159
225
  def handle_ping(packet, rxtime, peeraddr)
160
- if packet['E'] != 0
161
- $logger.error "Package displays an error message: #{packet['E']} Processing aborted."
162
- return
226
+ if !(packet.keys - %w[M Y E I T A S]).empty? ||
227
+ # M, Y are already checked in handle_packet from session manager
228
+ !packet['E'].is_a?(Integer) ||
229
+ !packet['I'].is_a?(Integer) ||
230
+ !packet['T'].is_a?(Time) ||
231
+ !packet['A'].is_a?(Array)
232
+ raise InvalidPacketError, 'The peer has sent an invalid message. The ping packet is incorrectly coded.'
163
233
  end
164
234
 
235
+ raise SignalizedError, "Package displays an error message: #{packet['E']} Processing aborted." if packet['E'] != 0
236
+
165
237
  id = packet['I']
166
238
  txtime = packet['T']
167
239
  remote_last_acks = packet['A']
168
240
 
241
+ if remote_last_acks.length != 32
242
+ raise InvalidMessageError, 'The peer has sent an invalid message. It does not contain any 32-Acks.'
243
+ end
244
+
245
+ remote_last_acks.each do |block_ack|
246
+ if !(block_ack.keys - %w[R U X]).empty? ||
247
+ !block_ack['R'].is_a?(Integer) ||
248
+ !block_ack['U'].is_a?(Time) ||
249
+ !block_ack['X'].is_a?(Time)
250
+ raise InvalidMessageError, 'The peer has sent an invalid message. An Ack is formatted invalid.'
251
+ end
252
+ end
253
+
169
254
  ack = {
170
255
  'R' => id,
171
256
  'U' => txtime,
@@ -1,18 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
  # sharable_constant_value: literal
3
3
 
4
+ # SPing / Splitted Ping is a protocol for measuring asymmetric latencies.
4
5
  module SPing
5
- class SessionManagerError < StandardError; end
6
- class OutOfSessions < SessionManagerError; end
6
+ # Inclusion of all possible SPing-specific errors.
7
+ require_relative 'errors'
7
8
 
9
+ # Container, which contains a collection of sessions and manages them.
8
10
  class SessionManager
9
11
  require 'socket'
10
12
  require 'timeout'
11
13
  require_relative 'session'
12
14
 
15
+ # Creates a new Session Manager, which can (and will) manage a number of sessions.
16
+ # @param host [#to_s] Host to which messages are to be bound and from which messages are sent accordingly.
17
+ # @param port [#to_i] Port
13
18
  def initialize(host = '::', port = 6924)
14
- @host = host
15
- @port = port
19
+ @host = host.to_s
20
+ @port = port.to_i
16
21
 
17
22
  @sessions = {}
18
23
  @sessions_mutex = Mutex.new
@@ -21,9 +26,12 @@ module SPing
21
26
  @socket.bind @host, @port
22
27
  end
23
28
 
29
+ # Initiates a new session with a peer.
30
+ # @param host [#to_s]
31
+ # @param port [#to_s, #to_i]
24
32
  def new_session(host, port = 6924)
25
33
  $logger.info "Add new session for host #{host} port #{port}."
26
- session = SPing::Session.new host, port, @socket, true
34
+ session = SPing::Session.new host.to_s, port.to_i, @socket, true
27
35
 
28
36
  counter = 0
29
37
  loop do
@@ -49,6 +57,8 @@ module SPing
49
57
  $logger.error "Out of session: #{e.message}"
50
58
  end
51
59
 
60
+ # Stops all threads connected to a session ID and removes the session from the Session Manager administration.
61
+ # @param session_id [Integer] Session ID of the session to be removed.
52
62
  def del_session(session_id)
53
63
  $logger.debug "Delete session with session id #{session_id}"
54
64
 
@@ -58,18 +68,21 @@ module SPing
58
68
  end
59
69
  end
60
70
 
71
+ # Stops all sessions or the threads that are connected to them.
61
72
  def stop_sessions
62
73
  @sessions.each do |_session_id, session|
63
74
  session.stop
64
75
  end
65
76
  end
66
77
 
78
+ # Waits and blocks until the Session Manager is no longer running.
67
79
  def join
68
80
  @runner&.join
69
81
  @gc_thread&.join
70
82
  @server_thread&.join
71
83
  end
72
84
 
85
+ # Starts the Session Manager.
73
86
  def run
74
87
  raise 'Client is already running.' if @runner
75
88
  raise 'GC is already running.' if @gc
@@ -89,6 +102,7 @@ module SPing
89
102
  end
90
103
  end
91
104
 
105
+ # Starts the server functionality of the Session Manager.
92
106
  def run_server
93
107
  raise 'Server is already running.' if @server_thread
94
108
  raise 'Server already exist.' if @server
@@ -106,6 +120,7 @@ module SPing
106
120
  return @server_thread
107
121
  end
108
122
 
123
+ # Stops the server functionality of the Session Manager.
109
124
  def stop_server
110
125
  raise 'Server thread is not running.' unless @server_thread
111
126
 
@@ -118,6 +133,7 @@ module SPing
118
133
  @server = nil
119
134
  end
120
135
 
136
+ # Stops the Session Manager. The server functionality must be stopped separately.
121
137
  def stop
122
138
  raise 'Session manager is not running.' unless @runner
123
139
 
@@ -130,6 +146,12 @@ module SPing
130
146
  @gc_thread = nil
131
147
  end
132
148
 
149
+ private
150
+
151
+ # Generates a new session ID that was not yet in use at the time of the check.
152
+ # If none could be generated within half a minute - for example because all
153
+ # session IDs have already been assigned - an error is thrown.
154
+ # @return [Integer]
133
155
  def generate_session_id
134
156
  Timeout.timeout(30, OutOfSessions, 'No session ID could be generated which has not yet been used.') do
135
157
  loop do
@@ -139,19 +161,23 @@ module SPing
139
161
  end
140
162
  end
141
163
 
142
- private
143
-
164
+ # Removes obsolete sessions. This includes sessions that are older than one minute but
165
+ # have not been activated a double time. And sessions that have not
166
+ # received any packets for half a minute.
144
167
  def do_gc
145
168
  $logger.debug 'Remove outdated sessions.'
146
169
  # rubocop:disable Style/HashEachMethods
147
- # You cannot run through a map and edit it at the same time. Since this is necessary due to the threading,
170
+ # You cannot run through a map and edit it at the same time. Since this is
171
+ # necessary due to the threading,
148
172
  # a snapshot of the key variable is run through.
149
173
  @sessions.keys.each do |session_id|
150
174
  # rubocop:enable Style/HashEachMethods
151
175
 
152
176
  session = @sessions[session_id]
153
- # It is possible that sessions no longer exist here, as we may have session IDs that have already been deleted.
154
- # However, we can ignore this aspect here, as we are the only function that deletes sessions.
177
+ # It is possible that sessions no longer exist here, as we may have
178
+ # session IDs that have already been deleted.
179
+ # However, we can ignore this aspect here, as we are the only
180
+ # function that deletes sessions.
155
181
  if !(session.tcp_handshake_complete && session.udp_handshake_complete)
156
182
  # Handshake incomplete
157
183
  if (Time.now.to_i - session.created.to_i) > 60
@@ -160,13 +186,16 @@ module SPing
160
186
  del_session session_id
161
187
  end
162
188
  elsif (Time.now.to_i - session.last_rx.to_i) > 30
163
- # 30 seconds have elapsed since the last ping from the peer was received. The peer is probably dead.
189
+ # 30 seconds have elapsed since the last ping from the peer was received.
190
+ # The peer is probably dead.
164
191
  $logger.debug "Session id #{session_id} without activity for over thirty seconds."
165
192
  del_session session_id
166
193
  end
167
194
  end
168
195
  end
169
196
 
197
+ # Receives a TCP handshake for a session and then has it
198
+ # managed by the Session Manager.
170
199
  def request_session(session)
171
200
  session.do_tcp_handshake2
172
201
 
@@ -180,6 +209,9 @@ module SPing
180
209
  return nil
181
210
  end
182
211
 
212
+ # Handler for a received packet. The function catches errors and outputs them.
213
+ # It also forwards the packet to the corresponding handler according to the
214
+ # session ID and type of packet.
183
215
  def handle_packet(buf)
184
216
  rxtime = Time.now
185
217
 
@@ -187,12 +219,20 @@ module SPing
187
219
  peerport = buf[1][1]
188
220
  $logger.debug "Packet received from #{peeraddr} port #{peerport}."
189
221
 
190
- packet = MessagePack.unpack(buf[0])
222
+ packet = MessagePack.unpack(buf[0]).to_h
223
+
224
+ if !packet.key?('M') ||
225
+ !packet.key?('Y') ||
226
+ !packet.key?('S') ||
227
+ !packet['M'].is_a?(Integer) ||
228
+ !packet['Y'].is_a?(Integer) ||
229
+ !packet['S'].is_a?(Integer)
230
+ raise InvalidMessageError, 'The peer has sent an invalid message. The packet is incorrectly coded.'
231
+ end
191
232
 
192
233
  # Check whether the Magic Number is the correct one.
193
234
  if packet['M'] != 11_181
194
- $logger.warn 'Package contains incorrect magic number. Processing is canceled.'
195
- return
235
+ raise UnknownPacketError, 'Package contains incorrect magic number. Processing is canceled.'
196
236
  end
197
237
 
198
238
  type = packet['Y'].chr
@@ -202,54 +242,60 @@ module SPing
202
242
  when 't'
203
243
  handle_ping packet, rxtime, peeraddr, peerport
204
244
  else
205
- $logger.warn "Package is of unknown type: #{type}"
245
+ raise UnknownPacketError, "Package is of unknown type: #{type}"
206
246
  end
247
+ rescue MessagePack::MalformedFormatError => e
248
+ $logger.error "Packet cannot be decoded: #{e.message}"
249
+ rescue PeerError => e
250
+ $logger.error "Perr error: #{e.message}"
207
251
  end
208
252
 
253
+ # Handler for a receiving UDP handshake.
209
254
  def handle_udp_handshake(packet, peeraddr, peerport)
255
+ if !(packet.keys - %w[M Y V S]).empty? ||
256
+ # M, Y are already checked in handle_packet from session manager
257
+ !packet['V'].is_a?(Integer)
258
+ raise InvalidMessageError, 'The peer has sent an invalid message. The handshake packet is incorrectly coded.'
259
+ end
260
+
210
261
  session_id = packet['S']
211
262
  session = @sessions[session_id]
212
- if session
213
- if packet['V'] != 3
214
- $logger.warn "UDP handshake uses an unsupported version: #{packet['V']}"
215
- return
216
- end
263
+ raise UnknownSessionError, 'UDP handshake received for uninitiated session.' unless session
264
+ raise UnsupportedVersionError, "UDP handshake uses an unsupported version: #{packet['V']}" if packet['V'] != 3
217
265
 
218
- session.set_endpoint peeraddr, peerport
266
+ session.set_endpoint peeraddr, peerport
219
267
 
220
- unless session.madebyme
221
- # If the session was not started by me, the other peer expects a
222
- # confirmation of the handshake in which the same is sent back.
223
- $logger.debug "Send UDP Handshake back for session id #{session_id}."
224
- session.send_udp_handshake
225
- end
226
-
227
- if session.udp_handshake_complete
228
- $logger.warn 'UDP handshake is received, although a previous one was already successful.'
229
- return
230
- end
231
-
232
- session.udp_handshake_recived
233
- $logger.info "UDP handshake for session ID #{session_id} was successful."
268
+ unless session.madebyme
269
+ # If the session was not started by me, the other peer expects a
270
+ # confirmation of the handshake in which the same is sent back.
271
+ $logger.debug "Send UDP Handshake back for session id #{session_id}."
272
+ session.send_udp_handshake
273
+ end
234
274
 
235
- session.start_pinger
236
- else
237
- $logger.warn 'UDP handshake received for uninitiated session.'
275
+ if session.udp_handshake_complete
276
+ $logger.warn 'UDP handshake is received, although a previous one was already successful.'
238
277
  return
239
278
  end
279
+
280
+ session.udp_handshake_recived
281
+ $logger.info "UDP handshake for session ID #{session_id} was successful."
282
+
283
+ session.start_pinger
240
284
  end
241
285
 
286
+ # Handler for a receiving ping or a receiving time message.
242
287
  def handle_ping(packet, rxtime, peeraddr, peerport)
243
288
  session_id = packet['S']
244
289
  session = @sessions[session_id]
245
- if session&.tcp_handshake_complete && session&.udp_handshake_complete
246
- session.set_endpoint peeraddr, peerport
247
- session.handle_ping packet, rxtime, peeraddr
248
- else
249
- $logger.warn "Ping packet received for non-initiated session id #{session_id}."
290
+ unless session&.tcp_handshake_complete && session&.udp_handshake_complete
291
+ raise UnknownSessionError, "Ping packet received for non-initiated session id #{session_id}."
250
292
  end
293
+
294
+ session.set_endpoint peeraddr, peerport
295
+ session.handle_ping packet, rxtime, peeraddr
251
296
  end
252
297
 
298
+ # Handler to establish a session for the request of a peer.
253
299
  def handle_client(client)
254
300
  host = client.peeraddr[3]
255
301
  port = client.peeraddr[1]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sping
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marek Küthe
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-11-09 00:00:00.000000000 Z
11
+ date: 2023-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -30,8 +30,9 @@ dependencies:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
32
  version: 1.7.2
33
- description: This is a reimplementation of [sping](https://github.com/benjojo/sping/)
34
- in Ruby.
33
+ description: This is a reimplementation in Ruby of the reference implementation of
34
+ the sping protocol in Go. The program provides both the client and server part to
35
+ measure asymmetric latencies between two peers.
35
36
  email: m.k@mk16.de
36
37
  executables:
37
38
  - sping
@@ -43,6 +44,7 @@ files:
43
44
  - LICENSE
44
45
  - README.md
45
46
  - bin/sping
47
+ - lib/errors.rb
46
48
  - lib/last_acks.rb
47
49
  - lib/session.rb
48
50
  - lib/session_manager.rb
@@ -68,8 +70,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
68
70
  - !ruby/object:Gem::Version
69
71
  version: '0'
70
72
  requirements: []
71
- rubygems_version: 3.4.21
73
+ rubygems_version: 3.4.22
72
74
  signing_key:
73
75
  specification_version: 4
74
- summary: Reimplementation of sping in Ruby.
76
+ summary: Program for measuring asymmetric latencies.
75
77
  test_files: []