lanet 0.5.1 → 1.0.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.
@@ -3,71 +3,93 @@
3
3
  require "openssl"
4
4
  require "digest"
5
5
  require "base64"
6
+ require_relative "config"
6
7
  require_relative "signer"
7
8
 
8
9
  module Lanet
10
+ # Encryptor class for message encryption/decryption and signing
9
11
  class Encryptor
10
- # Constants
11
- CIPHER_ALGORITHM = "AES-256-CBC"
12
+ # Message type prefixes
12
13
  ENCRYPTED_PREFIX = "E"
13
14
  PLAINTEXT_PREFIX = "P"
14
15
  SIGNED_ENCRYPTED_PREFIX = "SE"
15
16
  SIGNED_PLAINTEXT_PREFIX = "SP"
16
- IV_SIZE = 16
17
+
18
+ # Delimiters and sizes
17
19
  SIGNATURE_DELIMITER = "||SIG||"
18
- MAX_KEY_LENGTH = 64
19
20
 
20
21
  # Error class for encryption/decryption failures
21
22
  class Error < StandardError; end
22
23
 
24
+ # Message type enumeration
25
+ module MessageType
26
+ ENCRYPTED = :encrypted
27
+ PLAINTEXT = :plaintext
28
+ SIGNED_ENCRYPTED = :signed_encrypted
29
+ SIGNED_PLAINTEXT = :signed_plaintext
30
+ end
31
+
23
32
  # Prepares a message with encryption and/or signing
24
33
  # @param message [String] the message to prepare
25
34
  # @param encryption_key [String, nil] encryption key or nil for plaintext
26
35
  # @param private_key [String, nil] PEM-encoded private key for signing or nil for unsigned
27
36
  # @return [String] prepared message with appropriate prefix
28
37
  def self.prepare_message(message, encryption_key, private_key = nil)
29
- if private_key.nil? || private_key.empty?
30
- prepare_unsigned_message(message, encryption_key)
31
- else
32
- # Sign the message
33
- signature = Signer.sign(message.to_s, private_key)
34
- message_with_signature = "#{message}#{SIGNATURE_DELIMITER}#{signature}"
38
+ message_str = message.to_s
39
+ has_encryption = !encryption_key.nil? && !encryption_key.empty?
40
+ has_signature = !private_key.nil? && !private_key.empty?
41
+
42
+ case [has_signature, has_encryption]
43
+ when [false, false]
44
+ prepare_plaintext(message_str)
45
+ when [false, true]
46
+ prepare_encrypted(message_str, encryption_key)
47
+ when [true, false]
48
+ prepare_signed_plaintext(message_str, private_key)
49
+ when [true, true]
50
+ prepare_signed_encrypted(message_str, encryption_key, private_key)
51
+ end
52
+ end
35
53
 
36
- return "#{SIGNED_PLAINTEXT_PREFIX}#{message_with_signature}" if encryption_key.nil? || encryption_key.empty?
54
+ # Prepare a plaintext message
55
+ def self.prepare_plaintext(message)
56
+ "#{PLAINTEXT_PREFIX}#{message}"
57
+ end
37
58
 
38
- # Signed but not encrypted
59
+ # Prepare an encrypted but unsigned message
60
+ def self.prepare_encrypted(message, key)
61
+ encrypted_data = encrypt_data(message, key)
62
+ "#{ENCRYPTED_PREFIX}#{encrypted_data}"
63
+ end
39
64
 
40
- # Signed and encrypted
41
- begin
42
- cipher = OpenSSL::Cipher.new("AES-128-CBC")
43
- cipher.encrypt
44
- cipher.key = derive_key(encryption_key)
45
- iv = cipher.random_iv
46
- encrypted = cipher.update(message_with_signature) + cipher.final
47
- encoded = Base64.strict_encode64(iv + encrypted)
48
- "#{SIGNED_ENCRYPTED_PREFIX}#{encoded}"
49
- rescue StandardError => e
50
- raise Error, "Encryption failed: #{e.message}"
51
- end
65
+ # Prepare a signed but unencrypted message
66
+ def self.prepare_signed_plaintext(message, private_key)
67
+ signature = Signer.sign(message, private_key)
68
+ message_with_signature = "#{message}#{SIGNATURE_DELIMITER}#{signature}"
69
+ "#{SIGNED_PLAINTEXT_PREFIX}#{message_with_signature}"
70
+ end
52
71
 
53
- end
72
+ # Prepare a signed and encrypted message
73
+ def self.prepare_signed_encrypted(message, encryption_key, private_key)
74
+ signature = Signer.sign(message, private_key)
75
+ message_with_signature = "#{message}#{SIGNATURE_DELIMITER}#{signature}"
76
+ encrypted_data = encrypt_data(message_with_signature, encryption_key)
77
+ "#{SIGNED_ENCRYPTED_PREFIX}#{encrypted_data}"
54
78
  end
55
79
 
56
- # Original prepare_message renamed
57
- def self.prepare_unsigned_message(message, key)
58
- return PLAINTEXT_PREFIX + message.to_s if key.nil? || key.empty?
59
-
60
- begin
61
- cipher = OpenSSL::Cipher.new("AES-128-CBC")
62
- cipher.encrypt
63
- cipher.key = derive_key(key)
64
- iv = cipher.random_iv
65
- encrypted = cipher.update(message.to_s) + cipher.final
66
- encoded = Base64.strict_encode64(iv + encrypted)
67
- "#{ENCRYPTED_PREFIX}#{encoded}"
68
- rescue StandardError => e
69
- raise Error, "Encryption failed: #{e.message}"
70
- end
80
+ # Encrypt data with the given key
81
+ # @param data [String] data to encrypt
82
+ # @param key [String] encryption key
83
+ # @return [String] base64-encoded encrypted data with IV
84
+ def self.encrypt_data(data, key)
85
+ cipher = OpenSSL::Cipher.new(Config::CIPHER_ALGORITHM)
86
+ cipher.encrypt
87
+ cipher.key = derive_key(key)
88
+ iv = cipher.random_iv
89
+ encrypted = cipher.update(data) + cipher.final
90
+ Base64.strict_encode64(iv + encrypted)
91
+ rescue StandardError => e
92
+ raise Error, "Encryption failed: #{e.message}"
71
93
  end
72
94
 
73
95
  # Processes a message, decrypting and verifying if necessary
@@ -78,42 +100,68 @@ module Lanet
78
100
  def self.process_message(data, encryption_key = nil, public_key = nil)
79
101
  return { content: "[Empty message]", verified: false } if data.nil? || data.empty?
80
102
 
81
- prefix = data[0..0] # First character for simple prefixes
82
- prefix = data[0..1] if data.length > 1 && %w[SE SP].include?(data[0..1]) # Two characters for complex prefixes
83
- content = data[prefix.length..]
103
+ message_type, content = parse_message_type(data)
84
104
 
85
- case prefix
86
- when ENCRYPTED_PREFIX
87
- if encryption_key.nil? || encryption_key.strip.empty?
88
- { content: "[Encrypted message received, but no key provided]", verified: false }
89
- else
90
- begin
91
- decrypted = decode_encrypted_message(content, encryption_key)
92
- { content: decrypted, verified: false }
93
- rescue StandardError => e
94
- { content: "Decryption failed: #{e.message}", verified: false }
95
- end
96
- end
97
- when PLAINTEXT_PREFIX
105
+ case message_type
106
+ when MessageType::ENCRYPTED
107
+ process_encrypted_message(content, encryption_key)
108
+ when MessageType::PLAINTEXT
98
109
  { content: content, verified: false }
99
- when SIGNED_ENCRYPTED_PREFIX
100
- if encryption_key.nil? || encryption_key.strip.empty?
101
- { content: "[Signed encrypted message received, but no encryption key provided]", verified: false }
102
- else
103
- begin
104
- decrypted = decode_encrypted_message(content, encryption_key)
105
- process_signed_content(decrypted, public_key)
106
- rescue StandardError => e
107
- { content: "Processing signed encrypted message failed: #{e.message}", verified: false }
108
- end
109
- end
110
- when SIGNED_PLAINTEXT_PREFIX
110
+ when MessageType::SIGNED_ENCRYPTED
111
+ process_signed_encrypted_message(content, encryption_key, public_key)
112
+ when MessageType::SIGNED_PLAINTEXT
111
113
  process_signed_content(content, public_key)
112
114
  else
113
115
  { content: "[Invalid message format]", verified: false }
114
116
  end
115
117
  end
116
118
 
119
+ # Parse the message type from the prefix
120
+ # @param data [String] the raw message data
121
+ # @return [Array<Symbol, String>] message type and content
122
+ def self.parse_message_type(data)
123
+ # Check for two-character prefixes first
124
+ if data.length > 1 && data[0..1] == SIGNED_ENCRYPTED_PREFIX
125
+ [MessageType::SIGNED_ENCRYPTED, data[2..]]
126
+ elsif data.length > 1 && data[0..1] == SIGNED_PLAINTEXT_PREFIX
127
+ [MessageType::SIGNED_PLAINTEXT, data[2..]]
128
+ elsif data[0] == ENCRYPTED_PREFIX
129
+ [MessageType::ENCRYPTED, data[1..]]
130
+ elsif data[0] == PLAINTEXT_PREFIX
131
+ [MessageType::PLAINTEXT, data[1..]]
132
+ else
133
+ [nil, data]
134
+ end
135
+ end
136
+
137
+ # Process an encrypted message
138
+ def self.process_encrypted_message(content, encryption_key)
139
+ if encryption_key.nil? || encryption_key.strip.empty?
140
+ { content: "[Encrypted message received, but no key provided]", verified: false }
141
+ else
142
+ begin
143
+ decrypted = decrypt_data(content, encryption_key)
144
+ { content: decrypted, verified: false }
145
+ rescue Error => e
146
+ { content: "Decryption failed: #{e.message}", verified: false }
147
+ end
148
+ end
149
+ end
150
+
151
+ # Process a signed and encrypted message
152
+ def self.process_signed_encrypted_message(content, encryption_key, public_key)
153
+ if encryption_key.nil? || encryption_key.strip.empty?
154
+ { content: "[Signed encrypted message received, but no encryption key provided]", verified: false }
155
+ else
156
+ begin
157
+ decrypted = decrypt_data(content, encryption_key)
158
+ process_signed_content(decrypted, public_key)
159
+ rescue Error => e
160
+ { content: "Processing signed encrypted message failed: #{e.message}", verified: false }
161
+ end
162
+ end
163
+ end
164
+
117
165
  # Process content that contains a signature
118
166
  def self.process_signed_content(content, public_key)
119
167
  if content.include?(SIGNATURE_DELIMITER)
@@ -135,28 +183,39 @@ module Lanet
135
183
  end
136
184
  end
137
185
 
186
+ # Derive a key from the provided password
187
+ # @param key [String] the password/key to derive from
188
+ # @return [String] derived key of appropriate size
138
189
  def self.derive_key(key)
139
- # Add validation to reject keys that are too long
140
- if key && key.length > MAX_KEY_LENGTH
141
- raise Error,
142
- "Encryption key is too long (maximum #{MAX_KEY_LENGTH} characters)"
143
- end
144
-
190
+ validate_key_length(key)
145
191
  digest = OpenSSL::Digest.new("SHA256")
146
- OpenSSL::PKCS5.pbkdf2_hmac(key, "salt", 1000, 16, digest)
192
+ OpenSSL::PKCS5.pbkdf2_hmac(key, "salt", 1000, Config::KEY_SIZE, digest)
193
+ end
194
+
195
+ # Validate key length
196
+ def self.validate_key_length(key)
197
+ return unless key && key.length > Config::MAX_KEY_LENGTH
198
+
199
+ raise Error, "Encryption key is too long (maximum #{Config::MAX_KEY_LENGTH} characters)"
147
200
  end
148
201
 
149
- def self.decode_encrypted_message(content, key)
202
+ # Decrypt encrypted content
203
+ # @param content [String] base64-encoded encrypted data with IV
204
+ # @param key [String] decryption key
205
+ # @return [String] decrypted data
206
+ def self.decrypt_data(content, key)
150
207
  decoded = Base64.strict_decode64(content)
151
- iv = decoded[0...16]
152
- ciphertext = decoded[16..]
208
+ iv = decoded[0...Config::IV_SIZE]
209
+ ciphertext = decoded[Config::IV_SIZE..]
153
210
 
154
- decipher = OpenSSL::Cipher.new("AES-128-CBC")
211
+ decipher = OpenSSL::Cipher.new(Config::CIPHER_ALGORITHM)
155
212
  decipher.decrypt
156
213
  decipher.key = derive_key(key)
157
214
  decipher.iv = iv
158
215
 
159
216
  decipher.update(ciphertext) + decipher.final
217
+ rescue StandardError => e
218
+ raise Error, "Decryption failed: #{e.message}"
160
219
  end
161
220
  end
162
221
  end
@@ -9,21 +9,16 @@ require "base64"
9
9
  require "json"
10
10
  require "socket"
11
11
  require "timeout"
12
+ require_relative "config"
13
+ require_relative "transfer_state"
12
14
 
13
15
  module Lanet
16
+ # FileTransfer handles secure file transmission over the network
14
17
  class FileTransfer
15
- # Constants
16
- # Use smaller chunks in tests to avoid "Message too long" errors
17
- CHUNK_SIZE = if ENV["LANET_TEST_CHUNK_SIZE"]
18
- ENV["LANET_TEST_CHUNK_SIZE"].to_i
19
- elsif ENV["RACK_ENV"] == "test"
20
- 8192 # 8KB in test environment
21
- else
22
- 65_536 # 64KB in production
23
- end
24
-
25
- MAX_RETRIES = 3
26
- TIMEOUT = ENV["RACK_ENV"] == "test" ? 2 : 10 # Seconds
18
+ # Use configuration constants
19
+ CHUNK_SIZE = Config::CHUNK_SIZE
20
+ MAX_RETRIES = Config::MAX_RETRIES
21
+ TIMEOUT = Config::FILE_TRANSFER_TIMEOUT
27
22
 
28
23
  # Message types
29
24
  FILE_HEADER = "FH" # File metadata
@@ -204,17 +199,19 @@ module Lanet
204
199
  def handle_file_header(sender_ip, message_data, active_transfers, encryption_key, callback)
205
200
  header = JSON.parse(message_data)
206
201
  transfer_id = header["id"]
207
- active_transfers[transfer_id] = {
202
+
203
+ # Create new transfer state
204
+ active_transfers[transfer_id] = TransferState.new(
205
+ transfer_id: transfer_id,
208
206
  sender_ip: sender_ip,
209
207
  file_name: header["name"],
210
208
  file_size: header["size"],
211
- expected_checksum: header["checksum"],
212
- temp_file: Tempfile.new([File.basename(header["name"], ".*"), File.extname(header["name"])]),
213
- chunks_received: 0,
214
- timestamp: Time.now
215
- }
209
+ expected_checksum: header["checksum"]
210
+ )
211
+
216
212
  ack_message = Lanet::Encryptor.prepare_message("#{FILE_ACK}#{transfer_id}", encryption_key)
217
213
  @sender.send_to(sender_ip, ack_message)
214
+
218
215
  callback&.call(:start, {
219
216
  transfer_id: transfer_id,
220
217
  sender_ip: sender_ip,
@@ -229,19 +226,18 @@ module Lanet
229
226
  chunk = JSON.parse(message_data)
230
227
  transfer_id = chunk["id"]
231
228
  transfer = active_transfers[transfer_id]
232
- if transfer && transfer[:sender_ip] == sender_ip
229
+
230
+ if transfer && transfer.sender_ip == sender_ip
233
231
  chunk_data = Base64.strict_decode64(chunk["data"])
234
- transfer[:temp_file].write(chunk_data)
235
- transfer[:chunks_received] += 1
236
- bytes_received = transfer[:temp_file].size
237
- progress = (bytes_received.to_f / transfer[:file_size] * 100).round(2)
232
+ transfer.write_chunk(chunk_data)
233
+
238
234
  callback&.call(:progress, {
239
235
  transfer_id: transfer_id,
240
236
  sender_ip: sender_ip,
241
- file_name: transfer[:file_name],
242
- progress: progress,
243
- bytes_received: bytes_received,
244
- total_bytes: transfer[:file_size]
237
+ file_name: transfer.file_name,
238
+ progress: transfer.progress,
239
+ bytes_received: transfer.bytes_received,
240
+ total_bytes: transfer.file_size
245
241
  })
246
242
  end
247
243
  rescue JSON::ParserError => e
@@ -252,32 +248,34 @@ module Lanet
252
248
  end_data = JSON.parse(message_data)
253
249
  transfer_id = end_data["id"]
254
250
  transfer = active_transfers[transfer_id]
255
- if transfer && transfer[:sender_ip] == sender_ip
256
- transfer[:temp_file].close
257
- calculated_checksum = calculate_file_checksum(transfer[:temp_file].path)
258
- if calculated_checksum == transfer[:expected_checksum]
259
- final_path = File.join(output_dir, transfer[:file_name])
260
- FileUtils.mv(transfer[:temp_file].path, final_path)
261
- ack_message = Lanet::Encryptor.prepare_message("#{FILE_ACK}#{transfer_id}", encryption_key)
262
- @sender.send_to(sender_ip, ack_message)
263
- callback&.call(:complete, {
264
- transfer_id: transfer_id,
265
- sender_ip: sender_ip,
266
- file_name: transfer[:file_name],
267
- file_path: final_path
268
- })
269
- else
270
- error_msg = "Checksum verification failed"
271
- send_error(sender_ip, transfer_id, error_msg, encryption_key)
272
- callback&.call(:error, {
273
- transfer_id: transfer_id,
274
- sender_ip: sender_ip,
275
- error: error_msg
276
- })
277
- end
278
- transfer[:temp_file].unlink
279
- active_transfers.delete(transfer_id)
251
+
252
+ return unless transfer && transfer.sender_ip == sender_ip
253
+
254
+ if transfer.verify_checksum
255
+ final_path = File.join(output_dir, transfer.file_name)
256
+ FileUtils.mv(transfer.temp_file.path, final_path)
257
+
258
+ ack_message = Lanet::Encryptor.prepare_message("#{FILE_ACK}#{transfer_id}", encryption_key)
259
+ @sender.send_to(sender_ip, ack_message)
260
+
261
+ callback&.call(:complete, {
262
+ transfer_id: transfer_id,
263
+ sender_ip: sender_ip,
264
+ file_name: transfer.file_name,
265
+ file_path: final_path
266
+ })
267
+ else
268
+ error_msg = "Checksum verification failed"
269
+ send_error(sender_ip, transfer_id, error_msg, encryption_key)
270
+ callback&.call(:error, {
271
+ transfer_id: transfer_id,
272
+ sender_ip: sender_ip,
273
+ error: error_msg
274
+ })
280
275
  end
276
+
277
+ transfer.cleanup
278
+ active_transfers.delete(transfer_id)
281
279
  rescue JSON::ParserError => e
282
280
  send_error(sender_ip, "unknown", "Invalid end marker format: #{e.message}", encryption_key)
283
281
  end
@@ -285,31 +283,25 @@ module Lanet
285
283
  def handle_file_error(sender_ip, message_data, active_transfers, callback)
286
284
  error_data = JSON.parse(message_data)
287
285
  transfer_id = error_data["id"]
288
- if callback && active_transfers[transfer_id]
289
- callback.call(:error, {
290
- transfer_id: transfer_id,
291
- sender_ip: sender_ip,
292
- error: error_data["message"]
293
- })
294
- if active_transfers[transfer_id]
295
- active_transfers[transfer_id][:temp_file].close
296
- active_transfers[transfer_id][:temp_file].unlink
297
- active_transfers.delete(transfer_id)
298
- end
299
- end
286
+ transfer = active_transfers[transfer_id]
287
+
288
+ return unless callback && transfer
289
+
290
+ callback.call(:error, {
291
+ transfer_id: transfer_id,
292
+ sender_ip: sender_ip,
293
+ error: error_data["message"]
294
+ })
295
+
296
+ transfer.cleanup
297
+ active_transfers.delete(transfer_id)
300
298
  rescue JSON::ParserError
301
299
  # Ignore malformed error messages
302
300
  end
303
301
 
304
302
  def cleanup_transfers(active_transfers)
305
- active_transfers.each_value do |transfer|
306
- transfer[:temp_file].close
307
- begin
308
- transfer[:temp_file].unlink
309
- rescue StandardError
310
- nil
311
- end
312
- end
303
+ active_transfers.each_value(&:cleanup)
304
+ active_transfers.clear
313
305
  end
314
306
  end
315
307
  end
@@ -1,21 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "socket"
4
+ require_relative "config"
4
5
 
5
6
  module Lanet
7
+ # Receiver class for UDP message reception
6
8
  class Receiver
9
+ class ReceiveError < StandardError; end
10
+
11
+ attr_reader :port
12
+
7
13
  def initialize(port)
8
14
  @port = port
9
- @socket = UDPSocket.new
10
- @socket.bind("0.0.0.0", @port)
15
+ @socket = nil
16
+ @closed = false
17
+ @running = false
18
+ initialize_socket
11
19
  end
12
20
 
13
- def listen(&block)
21
+ def listen(buffer_size: Config::SMALL_BUFFER, &block)
22
+ raise ReceiveError, "Receiver is closed" if @closed
23
+ raise ArgumentError, "Block is required" unless block_given?
24
+
25
+ @running = true
26
+
14
27
  loop do
15
- data, addr = @socket.recvfrom(1024)
16
- ip = addr[3]
17
- block.call(data, ip)
28
+ break unless @running
29
+
30
+ begin
31
+ data, addr = @socket.recvfrom(buffer_size)
32
+ ip = addr[3]
33
+ block.call(data, ip) if data && ip
34
+ rescue IOError, Errno::EBADF => e
35
+ break if @closed
36
+
37
+ raise ReceiveError, "Socket error: #{e.message}"
38
+ rescue StandardError => e
39
+ Config.logger.error("Error receiving message: #{e.message}")
40
+ # Continue listening despite errors
41
+ end
18
42
  end
43
+ rescue Interrupt
44
+ Config.logger.info("Receiver interrupted")
45
+ stop
46
+ ensure
47
+ close unless @closed
48
+ end
49
+
50
+ def stop
51
+ @running = false
52
+ end
53
+
54
+ def close
55
+ return if @closed
56
+
57
+ @running = false
58
+ @socket&.close
59
+ @closed = true
60
+ end
61
+
62
+ def closed?
63
+ @closed
64
+ end
65
+
66
+ private
67
+
68
+ def initialize_socket
69
+ @socket = UDPSocket.new
70
+ @socket.bind("0.0.0.0", @port)
71
+ rescue Errno::EADDRINUSE
72
+ raise ReceiveError, "Port #{@port} is already in use"
73
+ rescue StandardError => e
74
+ raise ReceiveError, "Failed to initialize socket: #{e.message}"
19
75
  end
20
76
  end
21
77
  end
data/lib/lanet/sender.rb CHANGED
@@ -3,19 +3,51 @@
3
3
  require "socket"
4
4
 
5
5
  module Lanet
6
+ # Sender class for UDP message transmission
6
7
  class Sender
8
+ class SendError < StandardError; end
9
+
7
10
  def initialize(port)
8
11
  @port = port
9
- @socket = UDPSocket.new
10
- @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
12
+ @socket = nil
13
+ @closed = false
14
+ initialize_socket
11
15
  end
12
16
 
13
17
  def send_to(target_ip, message)
18
+ raise SendError, "Sender is closed" if @closed
19
+ raise ArgumentError, "Invalid IP address" if target_ip.nil? || target_ip.empty?
20
+ raise ArgumentError, "Message cannot be nil" if message.nil?
21
+
14
22
  @socket.send(message, 0, target_ip, @port)
23
+ rescue Errno::ENETUNREACH, Errno::EHOSTUNREACH => e
24
+ raise SendError, "Network unreachable: #{e.message}"
25
+ rescue StandardError => e
26
+ raise SendError, "Failed to send message: #{e.message}"
15
27
  end
16
28
 
17
29
  def broadcast(message)
18
- @socket.send(message, 0, "255.255.255.255", @port)
30
+ send_to("255.255.255.255", message)
31
+ end
32
+
33
+ def close
34
+ return if @closed
35
+
36
+ @socket&.close
37
+ @closed = true
38
+ end
39
+
40
+ def closed?
41
+ @closed
42
+ end
43
+
44
+ private
45
+
46
+ def initialize_socket
47
+ @socket = UDPSocket.new
48
+ @socket.setsockopt(Socket::SOL_SOCKET, Socket::SO_BROADCAST, true)
49
+ rescue StandardError => e
50
+ raise SendError, "Failed to initialize socket: #{e.message}"
19
51
  end
20
52
  end
21
53
  end
data/lib/lanet/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Lanet
4
- VERSION = "0.5.1"
4
+ VERSION = "1.0.0"
5
5
  end
data/lib/lanet.rb CHANGED
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "lanet/version"
4
+ require "lanet/config"
4
5
  require "lanet/sender"
5
6
  require "lanet/receiver"
6
7
  require "lanet/scanner"
7
8
  require "lanet/encryptor"
9
+ require "lanet/transfer_state"
8
10
  require "lanet/cli"
9
11
  require "lanet/ping"
10
12
  require "lanet/file_transfer"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lanet
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Davide Santangelo
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-20 00:00:00.000000000 Z
11
+ date: 2025-10-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -68,7 +68,10 @@ dependencies:
68
68
  version: '3.0'
69
69
  description: Lanet provides a simple yet powerful API for LAN device discovery, secure
70
70
  messaging, and real-time network monitoring. Features include encrypted communications,
71
- network scanning, targeted and broadcast messaging, and host pinging capabilities.
71
+ network scanning, targeted and broadcast messaging, host pinging, secure file transfers,
72
+ mesh networking, and advanced traceroute capabilities. Version 1.0.0 brings significant
73
+ architectural improvements with better resource management, enhanced error handling,
74
+ and improved code quality while maintaining 100% backward compatibility.
72
75
  email:
73
76
  - davide.santangelo@example.com
74
77
  executables: