lanet 0.2.1 → 0.4.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 +4 -4
- data/CHANGELOG.md +23 -0
- data/Gemfile.lock +1 -1
- data/README.md +228 -16
- data/index.html +336 -104
- data/lib/lanet/cli.rb +183 -14
- data/lib/lanet/file_transfer.rb +315 -0
- data/lib/lanet/mesh.rb +493 -0
- data/lib/lanet/version.rb +1 -1
- data/lib/lanet.rb +42 -27
- metadata +4 -2
data/lib/lanet/cli.rb
CHANGED
@@ -178,11 +178,194 @@ module Lanet
|
|
178
178
|
puts "Share your public key with others who need to verify your messages."
|
179
179
|
end
|
180
180
|
|
181
|
+
desc "send-file", "Send a file to a specific target"
|
182
|
+
option :target, type: :string, required: true, desc: "Target IP address"
|
183
|
+
option :file, type: :string, required: true, desc: "File to send"
|
184
|
+
option :key, type: :string, desc: "Encryption key (optional)"
|
185
|
+
option :private_key_file, type: :string, desc: "Path to private key file for signing (optional)"
|
186
|
+
option :port, type: :numeric, default: 5001, desc: "Port number"
|
187
|
+
def send_file
|
188
|
+
unless File.exist?(options[:file]) && File.file?(options[:file])
|
189
|
+
puts "Error: File not found or is not a regular file: #{options[:file]}"
|
190
|
+
return
|
191
|
+
end
|
192
|
+
|
193
|
+
private_key = nil
|
194
|
+
if options[:private_key_file]
|
195
|
+
begin
|
196
|
+
private_key = File.read(options[:private_key_file])
|
197
|
+
puts "File will be digitally signed"
|
198
|
+
rescue StandardError => e
|
199
|
+
puts "Error reading private key file: #{e.message}"
|
200
|
+
return
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
file_transfer = Lanet::FileTransfer.new(options[:port])
|
205
|
+
|
206
|
+
puts "Sending file #{File.basename(options[:file])} to #{options[:target]}..."
|
207
|
+
puts "File size: #{File.size(options[:file])} bytes"
|
208
|
+
|
209
|
+
begin
|
210
|
+
file_transfer.send_file(
|
211
|
+
options[:target],
|
212
|
+
options[:file],
|
213
|
+
options[:key],
|
214
|
+
private_key
|
215
|
+
) do |progress, bytes, total|
|
216
|
+
# Update progress bar
|
217
|
+
print "\rProgress: #{progress}% (#{bytes}/#{total} bytes)"
|
218
|
+
end
|
219
|
+
|
220
|
+
puts "\nFile sent successfully!"
|
221
|
+
rescue Lanet::FileTransfer::Error => e
|
222
|
+
puts "\nError: #{e.message}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
desc "receive-file", "Listen for incoming files"
|
227
|
+
option :output, type: :string, default: "./received", desc: "Output directory for received files"
|
228
|
+
option :encryption_key, type: :string, desc: "Encryption key (optional)"
|
229
|
+
option :public_key_file, type: :string, desc: "Path to public key file for verification (optional)"
|
230
|
+
option :port, type: :numeric, default: 5001, desc: "Port number"
|
231
|
+
def receive_file
|
232
|
+
public_key = nil
|
233
|
+
if options[:public_key_file]
|
234
|
+
begin
|
235
|
+
public_key = File.read(options[:public_key_file])
|
236
|
+
puts "Digital signature verification enabled"
|
237
|
+
rescue StandardError => e
|
238
|
+
puts "Error reading public key file: #{e.message}"
|
239
|
+
return
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
output_dir = File.expand_path(options[:output])
|
244
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
245
|
+
|
246
|
+
puts "Listening for incoming files on port #{options[:port]}..."
|
247
|
+
puts "Files will be saved to #{output_dir}"
|
248
|
+
puts "Press Ctrl+C to stop"
|
249
|
+
|
250
|
+
file_transfer = Lanet::FileTransfer.new(options[:port])
|
251
|
+
|
252
|
+
begin
|
253
|
+
file_transfer.receive_file(
|
254
|
+
output_dir,
|
255
|
+
options[:encryption_key],
|
256
|
+
public_key
|
257
|
+
) do |event, data|
|
258
|
+
case event
|
259
|
+
when :start
|
260
|
+
puts "\nReceiving file: #{data[:file_name]} from #{data[:sender_ip]}"
|
261
|
+
puts "Size: #{data[:file_size]} bytes"
|
262
|
+
puts "Transfer ID: #{data[:transfer_id]}"
|
263
|
+
when :progress
|
264
|
+
print "\rProgress: #{data[:progress]}% (#{data[:bytes_received]}/#{data[:total_bytes]} bytes)"
|
265
|
+
when :complete
|
266
|
+
puts "\nFile received and saved to: #{data[:file_path]}"
|
267
|
+
when :error
|
268
|
+
puts "\nError during file transfer: #{data[:error]}"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
rescue Interrupt
|
272
|
+
puts "\nFile receiver stopped."
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
181
276
|
desc "version", "Display the version of Lanet"
|
182
277
|
def version
|
183
278
|
puts "Lanet version #{Lanet::VERSION}"
|
184
279
|
end
|
185
280
|
|
281
|
+
desc "mesh start", "Start a mesh network node"
|
282
|
+
option :port, type: :numeric, default: 5050, desc: "Port for mesh communication"
|
283
|
+
option :max_hops, type: :numeric, default: 10, desc: "Maximum number of hops for message routing"
|
284
|
+
def mesh_start
|
285
|
+
mesh = Lanet::Mesh.new(options[:port], options[:max_hops])
|
286
|
+
|
287
|
+
puts "Starting mesh network node with ID: #{mesh.node_id}"
|
288
|
+
puts "Listening on port: #{options[:port]}"
|
289
|
+
puts "Press Ctrl+C to stop"
|
290
|
+
|
291
|
+
mesh.start
|
292
|
+
|
293
|
+
# Keep the process running
|
294
|
+
begin
|
295
|
+
loop do
|
296
|
+
sleep 1
|
297
|
+
end
|
298
|
+
rescue Interrupt
|
299
|
+
puts "\nStopping mesh network node..."
|
300
|
+
mesh.stop
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
desc "mesh send", "Send a message through the mesh network"
|
305
|
+
option :target, type: :string, required: true, desc: "Target node ID"
|
306
|
+
option :message, type: :string, required: true, desc: "Message to send"
|
307
|
+
option :key, type: :string, desc: "Encryption key (optional)"
|
308
|
+
option :private_key_file, type: :string, desc: "Path to private key file for signing (optional)"
|
309
|
+
option :port, type: :numeric, default: 5050, desc: "Port for mesh communication"
|
310
|
+
def mesh_send
|
311
|
+
mesh = Lanet::Mesh.new(options[:port])
|
312
|
+
|
313
|
+
private_key = nil
|
314
|
+
if options[:private_key_file]
|
315
|
+
begin
|
316
|
+
private_key = File.read(options[:private_key_file])
|
317
|
+
puts "Message will be digitally signed"
|
318
|
+
rescue StandardError => e
|
319
|
+
puts "Error reading private key file: #{e.message}"
|
320
|
+
return
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Initialize the mesh network
|
325
|
+
mesh.start
|
326
|
+
|
327
|
+
begin
|
328
|
+
message_id = mesh.send_message(
|
329
|
+
options[:target],
|
330
|
+
options[:message],
|
331
|
+
options[:key],
|
332
|
+
private_key
|
333
|
+
)
|
334
|
+
|
335
|
+
puts "Message sent through mesh network"
|
336
|
+
puts "Message ID: #{message_id}"
|
337
|
+
rescue Lanet::Mesh::Error => e
|
338
|
+
puts "Error sending mesh message: #{e.message}"
|
339
|
+
ensure
|
340
|
+
mesh.stop
|
341
|
+
end
|
342
|
+
end
|
343
|
+
|
344
|
+
desc "mesh info", "Display information about the mesh network"
|
345
|
+
option :port, type: :numeric, default: 5050, desc: "Port for mesh communication"
|
346
|
+
def mesh_info
|
347
|
+
mesh = Lanet::Mesh.new(options[:port])
|
348
|
+
|
349
|
+
# Load state if available
|
350
|
+
mesh.start
|
351
|
+
|
352
|
+
puts "Mesh Node ID: #{mesh.node_id}"
|
353
|
+
puts "\nConnected nodes:"
|
354
|
+
|
355
|
+
if mesh.connections.empty?
|
356
|
+
puts " No direct connections"
|
357
|
+
else
|
358
|
+
mesh.connections.each do |node_id, info|
|
359
|
+
last_seen = Time.now.to_i - info[:last_seen]
|
360
|
+
puts " #{node_id} (#{info[:ip]}, last seen #{last_seen}s ago)"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
puts "\nMessage cache: #{mesh.message_cache.size} messages"
|
365
|
+
|
366
|
+
mesh.stop
|
367
|
+
end
|
368
|
+
|
186
369
|
private
|
187
370
|
|
188
371
|
def display_ping_details(host, result)
|
@@ -259,19 +442,5 @@ module Lanet
|
|
259
442
|
puts "\nOutput:"
|
260
443
|
puts result[:output]
|
261
444
|
end
|
262
|
-
|
263
|
-
# Override method_missing to provide helpful error messages for common mistakes
|
264
|
-
def method_missing(method, *args)
|
265
|
-
if method.to_s == "ping" && args.any?
|
266
|
-
invoke "ping", [], { host: args.first, timeout: options[:timeout], count: options[:count],
|
267
|
-
quiet: options[:quiet], continuous: options[:continuous] }
|
268
|
-
else
|
269
|
-
super
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
def respond_to_missing?(method, include_private = false)
|
274
|
-
method.to_s == "ping" || super
|
275
|
-
end
|
276
445
|
end
|
277
446
|
end
|
@@ -0,0 +1,315 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest"
|
4
|
+
require "fileutils"
|
5
|
+
require "tempfile"
|
6
|
+
require "zlib"
|
7
|
+
require "securerandom"
|
8
|
+
require "base64"
|
9
|
+
require "json"
|
10
|
+
require "socket"
|
11
|
+
require "timeout"
|
12
|
+
|
13
|
+
module Lanet
|
14
|
+
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
|
27
|
+
|
28
|
+
# Message types
|
29
|
+
FILE_HEADER = "FH" # File metadata
|
30
|
+
FILE_CHUNK = "FC" # File data chunk
|
31
|
+
FILE_END = "FE" # End of transfer
|
32
|
+
FILE_ACK = "FA" # Acknowledgment
|
33
|
+
FILE_ERROR = "FR" # Error message
|
34
|
+
|
35
|
+
# Custom error class
|
36
|
+
class Error < StandardError; end
|
37
|
+
|
38
|
+
# Attributes for tracking progress
|
39
|
+
attr_reader :progress, :file_size, :transferred_bytes
|
40
|
+
|
41
|
+
### Initialization
|
42
|
+
def initialize(port = nil)
|
43
|
+
@port = port || 5001 # Default port for file transfers
|
44
|
+
@progress = 0.0
|
45
|
+
@file_size = 0
|
46
|
+
@transferred_bytes = 0
|
47
|
+
@sender = Lanet::Sender.new(@port) # Assumes Lanet::Sender is defined elsewhere
|
48
|
+
@cancellation_requested = false
|
49
|
+
end
|
50
|
+
|
51
|
+
### Send File Method
|
52
|
+
def send_file(target_ip, file_path, encryption_key = nil, private_key = nil, progress_callback = nil)
|
53
|
+
# Validate file
|
54
|
+
unless File.exist?(file_path) && File.file?(file_path)
|
55
|
+
raise Error, "File not found or is not a regular file: #{file_path}"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Initialize transfer state
|
59
|
+
@file_size = File.size(file_path)
|
60
|
+
@transferred_bytes = 0
|
61
|
+
@progress = 0.0
|
62
|
+
@cancellation_requested = false
|
63
|
+
transfer_id = SecureRandom.uuid
|
64
|
+
chunk_index = 0
|
65
|
+
|
66
|
+
receiver = nil
|
67
|
+
|
68
|
+
begin
|
69
|
+
# Send file header
|
70
|
+
file_name = File.basename(file_path)
|
71
|
+
file_checksum = calculate_file_checksum(file_path)
|
72
|
+
header_data = {
|
73
|
+
id: transfer_id,
|
74
|
+
name: file_name,
|
75
|
+
size: @file_size,
|
76
|
+
checksum: file_checksum,
|
77
|
+
timestamp: Time.now.to_i
|
78
|
+
}.to_json
|
79
|
+
header_message = Lanet::Encryptor.prepare_message("#{FILE_HEADER}#{header_data}", encryption_key, private_key)
|
80
|
+
@sender.send_to(target_ip, header_message)
|
81
|
+
|
82
|
+
# Wait for initial ACK
|
83
|
+
receiver = UDPSocket.new
|
84
|
+
receiver.bind("0.0.0.0", @port)
|
85
|
+
wait_for_ack(receiver, target_ip, transfer_id, encryption_key, "initial")
|
86
|
+
|
87
|
+
# Send file chunks
|
88
|
+
File.open(file_path, "rb") do |file|
|
89
|
+
until file.eof? || @cancellation_requested
|
90
|
+
chunk = file.read(CHUNK_SIZE)
|
91
|
+
chunk_data = {
|
92
|
+
id: transfer_id,
|
93
|
+
index: chunk_index,
|
94
|
+
data: Base64.strict_encode64(chunk)
|
95
|
+
}.to_json
|
96
|
+
chunk_message = Lanet::Encryptor.prepare_message("#{FILE_CHUNK}#{chunk_data}", encryption_key, private_key)
|
97
|
+
@sender.send_to(target_ip, chunk_message)
|
98
|
+
|
99
|
+
chunk_index += 1
|
100
|
+
@transferred_bytes += chunk.bytesize
|
101
|
+
@progress = (@transferred_bytes.to_f / @file_size * 100).round(2)
|
102
|
+
progress_callback&.call(@progress, @transferred_bytes, @file_size)
|
103
|
+
|
104
|
+
sleep(0.01) # Prevent overwhelming the receiver
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Send end marker and wait for final ACK
|
109
|
+
unless @cancellation_requested
|
110
|
+
end_data = { id: transfer_id, total_chunks: chunk_index }.to_json
|
111
|
+
end_message = Lanet::Encryptor.prepare_message("#{FILE_END}#{end_data}", encryption_key, private_key)
|
112
|
+
@sender.send_to(target_ip, end_message)
|
113
|
+
wait_for_ack(receiver, target_ip, transfer_id, encryption_key, "final")
|
114
|
+
true # Transfer successful
|
115
|
+
end
|
116
|
+
rescue StandardError => e
|
117
|
+
send_error(target_ip, transfer_id, e.message, encryption_key, private_key)
|
118
|
+
raise Error, "File transfer failed: #{e.message}"
|
119
|
+
ensure
|
120
|
+
receiver&.close
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
### Receive File Method
|
125
|
+
def receive_file(output_dir, encryption_key = nil, public_key = nil, progress_callback = nil, &block)
|
126
|
+
# Use the block parameter if provided and progress_callback is nil
|
127
|
+
progress_callback = block if block_given? && progress_callback.nil?
|
128
|
+
|
129
|
+
FileUtils.mkdir_p(output_dir) unless Dir.exist?(output_dir)
|
130
|
+
receiver = UDPSocket.new
|
131
|
+
receiver.bind("0.0.0.0", @port)
|
132
|
+
active_transfers = {}
|
133
|
+
|
134
|
+
begin
|
135
|
+
loop do
|
136
|
+
data, addr = receiver.recvfrom(65_536) # Large buffer for chunks
|
137
|
+
|
138
|
+
# Skip if we received nil data or address
|
139
|
+
next if addr.nil? || data.nil?
|
140
|
+
|
141
|
+
sender_ip = addr[3]
|
142
|
+
result = Lanet::Encryptor.process_message(data, encryption_key, public_key)
|
143
|
+
next unless result[:content]&.length&.> 2
|
144
|
+
|
145
|
+
message_type = result[:content][0..1]
|
146
|
+
message_data = result[:content][2..]
|
147
|
+
|
148
|
+
case message_type
|
149
|
+
when FILE_HEADER
|
150
|
+
handle_file_header(sender_ip, message_data, active_transfers, encryption_key, progress_callback)
|
151
|
+
when FILE_CHUNK
|
152
|
+
handle_file_chunk(sender_ip, message_data, active_transfers, progress_callback, encryption_key)
|
153
|
+
when FILE_END
|
154
|
+
handle_file_end(sender_ip, message_data, active_transfers, output_dir, encryption_key, progress_callback)
|
155
|
+
when FILE_ERROR
|
156
|
+
handle_file_error(sender_ip, message_data, active_transfers, progress_callback)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
rescue Interrupt
|
160
|
+
puts "\nFile receiver stopped."
|
161
|
+
ensure
|
162
|
+
cleanup_transfers(active_transfers)
|
163
|
+
receiver.close
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
### Cancel Transfer
|
168
|
+
def cancel_transfer
|
169
|
+
@cancellation_requested = true
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
### Helper Methods
|
175
|
+
|
176
|
+
def calculate_file_checksum(file_path)
|
177
|
+
Digest::SHA256.file(file_path).hexdigest
|
178
|
+
end
|
179
|
+
|
180
|
+
def send_error(target_ip, transfer_id, message, encryption_key, private_key = nil)
|
181
|
+
error_data = { id: transfer_id, message: message, timestamp: Time.now.to_i }.to_json
|
182
|
+
error_message = Lanet::Encryptor.prepare_message("#{FILE_ERROR}#{error_data}", encryption_key, private_key)
|
183
|
+
@sender.send_to(target_ip, error_message)
|
184
|
+
end
|
185
|
+
|
186
|
+
def wait_for_ack(receiver, target_ip, transfer_id, encryption_key, context)
|
187
|
+
Timeout.timeout(TIMEOUT) do
|
188
|
+
data, addr = receiver.recvfrom(1024)
|
189
|
+
sender_ip = addr[3]
|
190
|
+
if sender_ip == target_ip
|
191
|
+
result = Lanet::Encryptor.process_message(data, encryption_key)
|
192
|
+
return if result[:content]&.start_with?(FILE_ACK) && result[:content][2..] == transfer_id
|
193
|
+
|
194
|
+
# Valid ACK received
|
195
|
+
|
196
|
+
raise Error, "Invalid #{context} ACK received: #{result[:content]}"
|
197
|
+
|
198
|
+
end
|
199
|
+
end
|
200
|
+
rescue Timeout::Error
|
201
|
+
raise Error, "Timeout waiting for #{context} transfer acknowledgment"
|
202
|
+
end
|
203
|
+
|
204
|
+
def handle_file_header(sender_ip, message_data, active_transfers, encryption_key, callback)
|
205
|
+
header = JSON.parse(message_data)
|
206
|
+
transfer_id = header["id"]
|
207
|
+
active_transfers[transfer_id] = {
|
208
|
+
sender_ip: sender_ip,
|
209
|
+
file_name: header["name"],
|
210
|
+
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
|
+
}
|
216
|
+
ack_message = Lanet::Encryptor.prepare_message("#{FILE_ACK}#{transfer_id}", encryption_key)
|
217
|
+
@sender.send_to(sender_ip, ack_message)
|
218
|
+
callback&.call(:start, {
|
219
|
+
transfer_id: transfer_id,
|
220
|
+
sender_ip: sender_ip,
|
221
|
+
file_name: header["name"],
|
222
|
+
file_size: header["size"]
|
223
|
+
})
|
224
|
+
rescue JSON::ParserError => e
|
225
|
+
send_error(sender_ip, "unknown", "Invalid header format: #{e.message}", encryption_key)
|
226
|
+
end
|
227
|
+
|
228
|
+
def handle_file_chunk(sender_ip, message_data, active_transfers, callback, encryption_key)
|
229
|
+
chunk = JSON.parse(message_data)
|
230
|
+
transfer_id = chunk["id"]
|
231
|
+
transfer = active_transfers[transfer_id]
|
232
|
+
if transfer && transfer[:sender_ip] == sender_ip
|
233
|
+
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)
|
238
|
+
callback&.call(:progress, {
|
239
|
+
transfer_id: transfer_id,
|
240
|
+
sender_ip: sender_ip,
|
241
|
+
file_name: transfer[:file_name],
|
242
|
+
progress: progress,
|
243
|
+
bytes_received: bytes_received,
|
244
|
+
total_bytes: transfer[:file_size]
|
245
|
+
})
|
246
|
+
end
|
247
|
+
rescue JSON::ParserError => e
|
248
|
+
send_error(sender_ip, "unknown", "Invalid chunk format: #{e.message}", encryption_key)
|
249
|
+
end
|
250
|
+
|
251
|
+
def handle_file_end(sender_ip, message_data, active_transfers, output_dir, encryption_key, callback)
|
252
|
+
end_data = JSON.parse(message_data)
|
253
|
+
transfer_id = end_data["id"]
|
254
|
+
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)
|
280
|
+
end
|
281
|
+
rescue JSON::ParserError => e
|
282
|
+
send_error(sender_ip, "unknown", "Invalid end marker format: #{e.message}", encryption_key)
|
283
|
+
end
|
284
|
+
|
285
|
+
def handle_file_error(sender_ip, message_data, active_transfers, callback)
|
286
|
+
error_data = JSON.parse(message_data)
|
287
|
+
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
|
300
|
+
rescue JSON::ParserError
|
301
|
+
# Ignore malformed error messages
|
302
|
+
end
|
303
|
+
|
304
|
+
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
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|