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/mesh.rb
ADDED
@@ -0,0 +1,493 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "securerandom"
|
4
|
+
require "set"
|
5
|
+
require "json"
|
6
|
+
require "fileutils"
|
7
|
+
require "logger"
|
8
|
+
|
9
|
+
module Lanet
|
10
|
+
class Mesh
|
11
|
+
class Error < StandardError; end
|
12
|
+
|
13
|
+
DEFAULT_TTL = 10
|
14
|
+
DEFAULT_MESH_PORT = 5050
|
15
|
+
DEFAULT_DISCOVERY_INTERVAL = 60 # seconds
|
16
|
+
DEFAULT_MESSAGE_EXPIRY = 600 # 10 minutes
|
17
|
+
DEFAULT_CONNECTION_TIMEOUT = 180 # 3x discovery interval
|
18
|
+
|
19
|
+
MESSAGE_TYPES = {
|
20
|
+
discovery: "DISCOVERY",
|
21
|
+
discovery_response: "DISCOVERY_RESPONSE",
|
22
|
+
message: "MESSAGE",
|
23
|
+
route: "ROUTE_INFO"
|
24
|
+
}.freeze
|
25
|
+
|
26
|
+
attr_reader :node_id, :connections, :message_cache, :logger
|
27
|
+
|
28
|
+
def initialize(port = DEFAULT_MESH_PORT, max_hops = DEFAULT_TTL,
|
29
|
+
discovery_interval = DEFAULT_DISCOVERY_INTERVAL,
|
30
|
+
message_expiry = DEFAULT_MESSAGE_EXPIRY,
|
31
|
+
logger = nil)
|
32
|
+
@port = port
|
33
|
+
@max_hops = max_hops
|
34
|
+
@discovery_interval = discovery_interval
|
35
|
+
@message_expiry = message_expiry
|
36
|
+
@connection_timeout = @discovery_interval * 3
|
37
|
+
@node_id = SecureRandom.uuid
|
38
|
+
@connections = {}
|
39
|
+
@routes = {}
|
40
|
+
@message_cache = Set.new
|
41
|
+
@message_timestamps = {}
|
42
|
+
@processed_message_count = 0
|
43
|
+
@mutex = Mutex.new
|
44
|
+
@logger = logger || Logger.new($stdout)
|
45
|
+
@logger.level = Logger::INFO
|
46
|
+
|
47
|
+
# Setup communication channels
|
48
|
+
@sender = Lanet::Sender.new(port)
|
49
|
+
@receiver = nil
|
50
|
+
|
51
|
+
# For handling data storage
|
52
|
+
@storage_path = File.join(Dir.home, ".lanet", "mesh")
|
53
|
+
FileUtils.mkdir_p(@storage_path) unless Dir.exist?(@storage_path)
|
54
|
+
end
|
55
|
+
|
56
|
+
def start
|
57
|
+
return if @running
|
58
|
+
|
59
|
+
@running = true
|
60
|
+
start_receiver
|
61
|
+
start_discovery_service
|
62
|
+
start_monitoring
|
63
|
+
start_cache_pruning
|
64
|
+
|
65
|
+
@logger.info("Mesh node #{@node_id} started on port #{@port}")
|
66
|
+
load_state
|
67
|
+
end
|
68
|
+
|
69
|
+
def stop
|
70
|
+
return unless @running
|
71
|
+
|
72
|
+
@logger.info("Stopping mesh node #{@node_id}")
|
73
|
+
@running = false
|
74
|
+
|
75
|
+
[@discovery_thread, @receiver_thread, @monitor_thread, @cache_pruning_thread].each do |thread|
|
76
|
+
thread&.exit
|
77
|
+
end
|
78
|
+
|
79
|
+
save_state
|
80
|
+
@logger.info("Mesh node stopped")
|
81
|
+
end
|
82
|
+
|
83
|
+
def send_message(target_id, message, encryption_key = nil, private_key = nil)
|
84
|
+
unless @connections.key?(target_id) || @routes.key?(target_id)
|
85
|
+
@logger.debug("No known route to #{target_id}, performing discovery")
|
86
|
+
perform_discovery
|
87
|
+
|
88
|
+
# Replace sleep with timeout-based approach
|
89
|
+
discovery_timeout = Time.now.to_i + 2
|
90
|
+
until @connections.key?(target_id) || @routes.key?(target_id) || Time.now.to_i > discovery_timeout
|
91
|
+
sleep 0.1 # Short sleep to avoid CPU spinning
|
92
|
+
end
|
93
|
+
|
94
|
+
unless @connections.key?(target_id) || @routes.key?(target_id)
|
95
|
+
@logger.error("No route to node #{target_id}")
|
96
|
+
raise Error, "No route to node #{target_id}"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
message_id = SecureRandom.uuid
|
101
|
+
|
102
|
+
# Prevent message loops by adding to cache
|
103
|
+
@mutex.synchronize do
|
104
|
+
@message_cache.add(message_id)
|
105
|
+
@message_timestamps[message_id] = Time.now.to_i
|
106
|
+
end
|
107
|
+
|
108
|
+
# Prepare the mesh message container
|
109
|
+
encrypted_content = encryption_key ? Encryptor.prepare_message(message, encryption_key, private_key) : message
|
110
|
+
mesh_message = build_mesh_message(
|
111
|
+
MESSAGE_TYPES[:message],
|
112
|
+
id: message_id,
|
113
|
+
target: target_id,
|
114
|
+
content: encrypted_content,
|
115
|
+
hops: 0
|
116
|
+
)
|
117
|
+
|
118
|
+
# Direct connection
|
119
|
+
if @connections.key?(target_id)
|
120
|
+
@logger.debug("Sending direct message to #{target_id}")
|
121
|
+
@sender.send_to(@connections[target_id][:ip], mesh_message.to_json)
|
122
|
+
return message_id
|
123
|
+
end
|
124
|
+
|
125
|
+
# Route through intermediate node
|
126
|
+
if @routes.key?(target_id)
|
127
|
+
next_hop = @routes[target_id][:next_hop]
|
128
|
+
if @connections.key?(next_hop)
|
129
|
+
@logger.debug("Sending message to #{target_id} via #{next_hop}")
|
130
|
+
@sender.send_to(@connections[next_hop][:ip], mesh_message.to_json)
|
131
|
+
return message_id
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Broadcast as last resort
|
136
|
+
@logger.debug("Broadcasting message to find route to #{target_id}")
|
137
|
+
broadcast_mesh_message(mesh_message)
|
138
|
+
message_id
|
139
|
+
end
|
140
|
+
|
141
|
+
def broadcast_mesh_message(mesh_message)
|
142
|
+
@connections.each do |id, info|
|
143
|
+
next if id == mesh_message[:origin]
|
144
|
+
|
145
|
+
@sender.send_to(info[:ip], mesh_message.to_json)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def healthy?
|
150
|
+
@running &&
|
151
|
+
@receiver_thread&.alive? &&
|
152
|
+
@discovery_thread&.alive? &&
|
153
|
+
@monitor_thread&.alive? &&
|
154
|
+
@cache_pruning_thread&.alive?
|
155
|
+
end
|
156
|
+
|
157
|
+
def stats
|
158
|
+
{
|
159
|
+
node_id: @node_id,
|
160
|
+
connections: @connections.size,
|
161
|
+
routes: @routes.size,
|
162
|
+
message_cache_size: @message_cache.size,
|
163
|
+
processed_messages: @processed_message_count
|
164
|
+
}
|
165
|
+
end
|
166
|
+
|
167
|
+
private
|
168
|
+
|
169
|
+
def build_mesh_message(type, extra_fields = {})
|
170
|
+
{
|
171
|
+
type: type,
|
172
|
+
id: extra_fields[:id] || SecureRandom.uuid,
|
173
|
+
origin: @node_id,
|
174
|
+
timestamp: Time.now.to_i
|
175
|
+
}.merge(extra_fields)
|
176
|
+
end
|
177
|
+
|
178
|
+
def start_receiver
|
179
|
+
@receiver = Lanet::Receiver.new(@port)
|
180
|
+
@receiver_thread = Thread.new do
|
181
|
+
@logger.info("Starting receiver on port #{@port}")
|
182
|
+
@receiver.listen do |data, sender_ip|
|
183
|
+
handle_incoming_data(data, sender_ip)
|
184
|
+
rescue StandardError => e
|
185
|
+
@logger.error("Error handling mesh message: #{e.message}")
|
186
|
+
@logger.error(e.backtrace.join("\n")) if @logger.debug?
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def start_monitoring
|
192
|
+
@monitor_thread = Thread.new do
|
193
|
+
@logger.info("Starting thread monitor")
|
194
|
+
|
195
|
+
while @running
|
196
|
+
unless @receiver_thread&.alive?
|
197
|
+
@logger.warn("Receiver thread died, restarting...")
|
198
|
+
start_receiver
|
199
|
+
end
|
200
|
+
|
201
|
+
unless @discovery_thread&.alive?
|
202
|
+
@logger.warn("Discovery thread died, restarting...")
|
203
|
+
start_discovery_service
|
204
|
+
end
|
205
|
+
|
206
|
+
unless @cache_pruning_thread&.alive?
|
207
|
+
@logger.warn("Cache pruning thread died, restarting...")
|
208
|
+
start_cache_pruning
|
209
|
+
end
|
210
|
+
|
211
|
+
sleep 30
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def start_cache_pruning
|
217
|
+
@cache_pruning_thread = Thread.new do
|
218
|
+
@logger.info("Starting cache pruning service")
|
219
|
+
|
220
|
+
while @running
|
221
|
+
prune_message_cache
|
222
|
+
sleep @discovery_interval
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def handle_incoming_data(data, sender_ip)
|
228
|
+
message = JSON.parse(data, symbolize_names: true)
|
229
|
+
|
230
|
+
# Track metrics
|
231
|
+
@mutex.synchronize { @processed_message_count += 1 }
|
232
|
+
|
233
|
+
# Discard messages older than configured expiry time
|
234
|
+
if message[:timestamp] < Time.now.to_i - @message_expiry
|
235
|
+
@logger.debug("Discarding expired message: #{message[:id]}")
|
236
|
+
return
|
237
|
+
end
|
238
|
+
|
239
|
+
# Skip messages we've already processed
|
240
|
+
if @message_cache.include?(message[:id])
|
241
|
+
@logger.debug("Skipping already processed message: #{message[:id]}")
|
242
|
+
return
|
243
|
+
end
|
244
|
+
|
245
|
+
# Add to cache to prevent loops
|
246
|
+
@mutex.synchronize do
|
247
|
+
@message_cache.add(message[:id])
|
248
|
+
@message_timestamps[message[:id]] = Time.now.to_i
|
249
|
+
end
|
250
|
+
|
251
|
+
# Dispatch to appropriate handler
|
252
|
+
case message[:type]
|
253
|
+
when MESSAGE_TYPES[:discovery]
|
254
|
+
handle_discovery(message, sender_ip)
|
255
|
+
when MESSAGE_TYPES[:discovery_response]
|
256
|
+
handle_discovery_response(message, sender_ip)
|
257
|
+
when MESSAGE_TYPES[:message]
|
258
|
+
handle_message(message, sender_ip)
|
259
|
+
when MESSAGE_TYPES[:route]
|
260
|
+
handle_route_info(message, sender_ip)
|
261
|
+
else
|
262
|
+
@logger.warn("Unknown message type: #{message[:type]}")
|
263
|
+
end
|
264
|
+
rescue JSON::ParserError => e
|
265
|
+
@logger.debug("Ignoring non-JSON message: #{e.message[0..100]}")
|
266
|
+
rescue StandardError => e
|
267
|
+
@logger.error("Error processing message: #{e.message}")
|
268
|
+
@logger.error(e.backtrace.join("\n")) if @logger.debug?
|
269
|
+
end
|
270
|
+
|
271
|
+
def handle_discovery(message, sender_ip)
|
272
|
+
# Add the sender to our connections
|
273
|
+
@mutex.synchronize do
|
274
|
+
@connections[message[:origin]] = {
|
275
|
+
ip: sender_ip,
|
276
|
+
last_seen: Time.now.to_i
|
277
|
+
}
|
278
|
+
end
|
279
|
+
|
280
|
+
@logger.debug("Added connection to #{message[:origin]} at #{sender_ip}")
|
281
|
+
|
282
|
+
# Send a discovery response
|
283
|
+
response = build_mesh_message(
|
284
|
+
MESSAGE_TYPES[:discovery_response],
|
285
|
+
target: message[:origin],
|
286
|
+
known_nodes: @connections.keys
|
287
|
+
)
|
288
|
+
|
289
|
+
@sender.send_to(sender_ip, response.to_json)
|
290
|
+
|
291
|
+
# Share route information
|
292
|
+
share_routes_with(message[:origin])
|
293
|
+
end
|
294
|
+
|
295
|
+
def handle_discovery_response(message, sender_ip)
|
296
|
+
@mutex.synchronize do
|
297
|
+
# Update our connection to the sender
|
298
|
+
@connections[message[:origin]] = {
|
299
|
+
ip: sender_ip,
|
300
|
+
last_seen: Time.now.to_i
|
301
|
+
}
|
302
|
+
|
303
|
+
# Add routes for known nodes with correct distance
|
304
|
+
message[:known_nodes].each do |node_id|
|
305
|
+
next if node_id == @node_id || @connections.key?(node_id)
|
306
|
+
|
307
|
+
@routes[node_id] = {
|
308
|
+
next_hop: message[:origin],
|
309
|
+
distance: 2, # Corrected to reflect hops through responder
|
310
|
+
last_updated: Time.now.to_i
|
311
|
+
}
|
312
|
+
|
313
|
+
@logger.debug("Added route to #{node_id} via #{message[:origin]} (distance: 2)")
|
314
|
+
end
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def handle_message(message, _sender_ip)
|
319
|
+
# If message is for us, process it
|
320
|
+
if message[:target] == @node_id
|
321
|
+
@logger.info("Received mesh message from #{message[:origin]}: #{message[:content]}")
|
322
|
+
return
|
323
|
+
end
|
324
|
+
|
325
|
+
# Otherwise, forward if we haven't exceeded max hops
|
326
|
+
if message[:hops] >= @max_hops
|
327
|
+
@logger.debug("Message exceeded max hops (#{@max_hops}), dropping")
|
328
|
+
return
|
329
|
+
end
|
330
|
+
|
331
|
+
message[:hops] += 1
|
332
|
+
@logger.debug("Forwarding message from #{message[:origin]} to #{message[:target]} (hop #{message[:hops]})")
|
333
|
+
|
334
|
+
if @connections.key?(message[:target])
|
335
|
+
@sender.send_to(@connections[message[:target]][:ip], message.to_json)
|
336
|
+
elsif @routes.key?(message[:target])
|
337
|
+
next_hop = @routes[message[:target]][:next_hop]
|
338
|
+
if @connections.key?(next_hop)
|
339
|
+
@sender.send_to(@connections[next_hop][:ip], message.to_json)
|
340
|
+
else
|
341
|
+
@logger.warn("Lost connection to next hop #{next_hop}, broadcasting")
|
342
|
+
broadcast_mesh_message(message)
|
343
|
+
end
|
344
|
+
else
|
345
|
+
broadcast_mesh_message(message)
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def handle_route_info(message, _sender_ip)
|
350
|
+
@mutex.synchronize do
|
351
|
+
message[:routes].each do |node_id, route_info|
|
352
|
+
next if node_id.to_s == @node_id || @connections.key?(node_id.to_s)
|
353
|
+
|
354
|
+
distance = route_info[:distance] + 1
|
355
|
+
|
356
|
+
# Only update if we don't have a route or the new route is better
|
357
|
+
next unless !@routes.key?(node_id.to_s) || @routes[node_id.to_s][:distance] > distance
|
358
|
+
|
359
|
+
@routes[node_id.to_s] = {
|
360
|
+
next_hop: message[:origin],
|
361
|
+
distance: distance,
|
362
|
+
last_updated: Time.now.to_i
|
363
|
+
}
|
364
|
+
|
365
|
+
@logger.debug("Updated route to #{node_id} via #{message[:origin]} (distance: #{distance})")
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def start_discovery_service
|
371
|
+
@discovery_thread = Thread.new do
|
372
|
+
@logger.info("Starting discovery service")
|
373
|
+
|
374
|
+
while @running
|
375
|
+
perform_discovery
|
376
|
+
prune_old_connections
|
377
|
+
sleep @discovery_interval
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
def perform_discovery
|
383
|
+
@logger.debug("Performing network discovery")
|
384
|
+
discovery_message = build_mesh_message(MESSAGE_TYPES[:discovery])
|
385
|
+
@sender.broadcast(discovery_message.to_json)
|
386
|
+
end
|
387
|
+
|
388
|
+
def share_routes_with(target_node_id)
|
389
|
+
return unless @connections.key?(target_node_id)
|
390
|
+
|
391
|
+
@logger.debug("Sharing route information with #{target_node_id}")
|
392
|
+
route_message = build_mesh_message(MESSAGE_TYPES[:route], routes: @routes)
|
393
|
+
@sender.send_to(@connections[target_node_id][:ip], route_message.to_json)
|
394
|
+
end
|
395
|
+
|
396
|
+
def prune_old_connections
|
397
|
+
now = Time.now.to_i
|
398
|
+
pruned_connections = 0
|
399
|
+
pruned_routes = 0
|
400
|
+
|
401
|
+
@mutex.synchronize do
|
402
|
+
# Remove old connections
|
403
|
+
@connections.each do |id, info|
|
404
|
+
next unless now - info[:last_seen] > @connection_timeout
|
405
|
+
|
406
|
+
@connections.delete(id)
|
407
|
+
pruned_connections += 1
|
408
|
+
@logger.debug("Pruned stale connection to #{id}")
|
409
|
+
end
|
410
|
+
|
411
|
+
# Remove old routes
|
412
|
+
@routes.each do |id, info|
|
413
|
+
next unless now - info[:last_updated] > @connection_timeout
|
414
|
+
|
415
|
+
@routes.delete(id)
|
416
|
+
pruned_routes += 1
|
417
|
+
@logger.debug("Pruned stale route to #{id}")
|
418
|
+
end
|
419
|
+
end
|
420
|
+
|
421
|
+
return unless pruned_connections.positive? || pruned_routes.positive?
|
422
|
+
|
423
|
+
@logger.info("Pruned #{pruned_connections} connections and #{pruned_routes} routes")
|
424
|
+
end
|
425
|
+
|
426
|
+
def prune_message_cache
|
427
|
+
now = Time.now.to_i
|
428
|
+
pruned_count = 0
|
429
|
+
|
430
|
+
@mutex.synchronize do
|
431
|
+
@message_timestamps.each do |msg_id, timestamp|
|
432
|
+
next unless now - timestamp > @message_expiry
|
433
|
+
|
434
|
+
@message_cache.delete(msg_id)
|
435
|
+
@message_timestamps.delete(msg_id)
|
436
|
+
pruned_count += 1
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
@logger.info("Pruned #{pruned_count} messages from cache") if pruned_count.positive?
|
441
|
+
end
|
442
|
+
|
443
|
+
def save_state
|
444
|
+
state = {
|
445
|
+
node_id: @node_id,
|
446
|
+
connections: @connections,
|
447
|
+
routes: @routes,
|
448
|
+
timestamp: Time.now.to_i
|
449
|
+
}
|
450
|
+
|
451
|
+
begin
|
452
|
+
File.write(File.join(@storage_path, "state.json"), state.to_json)
|
453
|
+
@logger.info("Mesh state saved successfully")
|
454
|
+
rescue StandardError => e
|
455
|
+
@logger.error("Failed to save mesh state: #{e.message}")
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
def load_state
|
460
|
+
state_file = File.join(@storage_path, "state.json")
|
461
|
+
return unless File.exist?(state_file)
|
462
|
+
|
463
|
+
begin
|
464
|
+
@logger.info("Loading mesh state from #{state_file}")
|
465
|
+
state = JSON.parse(File.read(state_file), symbolize_names: true)
|
466
|
+
validate_state(state)
|
467
|
+
|
468
|
+
@node_id = state[:node_id]
|
469
|
+
@connections = state[:connections]
|
470
|
+
@routes = state[:routes]
|
471
|
+
|
472
|
+
@logger.info("Mesh state loaded successfully, node ID: #{@node_id}")
|
473
|
+
rescue JSON::ParserError => e
|
474
|
+
@logger.error("Error parsing mesh state file: #{e.message}")
|
475
|
+
rescue KeyError => e
|
476
|
+
@logger.error("Invalid mesh state structure: #{e.message}")
|
477
|
+
rescue StandardError => e
|
478
|
+
@logger.error("Error loading mesh state: #{e.message}")
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
def validate_state(state)
|
483
|
+
%i[node_id connections routes timestamp].each do |key|
|
484
|
+
raise KeyError, "Missing required key: #{key}" unless state.key?(key)
|
485
|
+
end
|
486
|
+
|
487
|
+
# Verify timestamp is reasonable
|
488
|
+
return unless state[:timestamp] < Time.now.to_i - 30 * 24 * 60 * 60
|
489
|
+
|
490
|
+
@logger.warn("State file is more than 30 days old")
|
491
|
+
end
|
492
|
+
end
|
493
|
+
end
|
data/lib/lanet/version.rb
CHANGED
data/lib/lanet.rb
CHANGED
@@ -7,6 +7,8 @@ require "lanet/scanner"
|
|
7
7
|
require "lanet/encryptor"
|
8
8
|
require "lanet/cli"
|
9
9
|
require "lanet/ping"
|
10
|
+
require "lanet/file_transfer"
|
11
|
+
require "lanet/mesh"
|
10
12
|
|
11
13
|
module Lanet
|
12
14
|
class Error < StandardError; end
|
@@ -14,32 +16,45 @@ module Lanet
|
|
14
16
|
# Default port used for communication
|
15
17
|
DEFAULT_PORT = 5000
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
19
|
+
class << self
|
20
|
+
# Creates a new sender instance
|
21
|
+
def sender(port = DEFAULT_PORT)
|
22
|
+
Sender.new(port)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Creates a new receiver instance
|
26
|
+
def receiver(port = DEFAULT_PORT)
|
27
|
+
Receiver.new(port)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Creates a new scanner instance
|
31
|
+
def scanner
|
32
|
+
Scanner.new
|
33
|
+
end
|
34
|
+
|
35
|
+
# Helper to encrypt a message
|
36
|
+
def encrypt(message, key)
|
37
|
+
Encryptor.prepare_message(message, key)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Helper to decrypt a message
|
41
|
+
def decrypt(data, key)
|
42
|
+
result = Encryptor.process_message(data, key)
|
43
|
+
result[:content]
|
44
|
+
end
|
45
|
+
|
46
|
+
def pinger(timeout: 1, count: 3)
|
47
|
+
Ping.new(timeout: timeout, count: count)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Add file transfer functionality
|
51
|
+
def file_transfer(port = 5001)
|
52
|
+
FileTransfer.new(port)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Create a new mesh network instance
|
56
|
+
def mesh_network(port = 5050, max_hops = 10)
|
57
|
+
Mesh.new(port, max_hops)
|
58
|
+
end
|
44
59
|
end
|
45
60
|
end
|
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.
|
4
|
+
version: 0.4.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-
|
11
|
+
date: 2025-03-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -93,6 +93,8 @@ files:
|
|
93
93
|
- lib/lanet.rb
|
94
94
|
- lib/lanet/cli.rb
|
95
95
|
- lib/lanet/encryptor.rb
|
96
|
+
- lib/lanet/file_transfer.rb
|
97
|
+
- lib/lanet/mesh.rb
|
96
98
|
- lib/lanet/ping.rb
|
97
99
|
- lib/lanet/receiver.rb
|
98
100
|
- lib/lanet/scanner.rb
|