sping 1.0.3 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []