torchat 0.0.1.rc.1

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.
Files changed (43) hide show
  1. data/README.md +43 -0
  2. data/bin/torchatd +537 -0
  3. data/doc/protocol/broadcast.md +26 -0
  4. data/doc/protocol/groupchat.md +140 -0
  5. data/doc/protocol/latency.md +30 -0
  6. data/doc/protocol/standard.md +279 -0
  7. data/doc/protocol/typing.md +41 -0
  8. data/etc/torchat.yml +12 -0
  9. data/lib/torchat.rb +239 -0
  10. data/lib/torchat/protocol.rb +256 -0
  11. data/lib/torchat/protocol/broadcast.rb +40 -0
  12. data/lib/torchat/protocol/groupchat.rb +197 -0
  13. data/lib/torchat/protocol/latency.rb +44 -0
  14. data/lib/torchat/protocol/standard.rb +367 -0
  15. data/lib/torchat/protocol/typing.rb +36 -0
  16. data/lib/torchat/session.rb +603 -0
  17. data/lib/torchat/session/broadcast/message.rb +50 -0
  18. data/lib/torchat/session/broadcasts.rb +72 -0
  19. data/lib/torchat/session/buddies.rb +152 -0
  20. data/lib/torchat/session/buddy.rb +343 -0
  21. data/lib/torchat/session/buddy/joined_group_chat.rb +42 -0
  22. data/lib/torchat/session/buddy/joined_group_chats.rb +46 -0
  23. data/lib/torchat/session/buddy/latency.rb +54 -0
  24. data/lib/torchat/session/event.rb +79 -0
  25. data/lib/torchat/session/file_transfer.rb +173 -0
  26. data/lib/torchat/session/file_transfer/block.rb +51 -0
  27. data/lib/torchat/session/file_transfers.rb +89 -0
  28. data/lib/torchat/session/group_chat.rb +123 -0
  29. data/lib/torchat/session/group_chat/participant.rb +38 -0
  30. data/lib/torchat/session/group_chat/participants.rb +52 -0
  31. data/lib/torchat/session/group_chats.rb +74 -0
  32. data/lib/torchat/session/incoming.rb +187 -0
  33. data/lib/torchat/session/outgoing.rb +102 -0
  34. data/lib/torchat/tor.rb +107 -0
  35. data/lib/torchat/utils.rb +87 -0
  36. data/lib/torchat/version.rb +24 -0
  37. data/test/file_transfer/receiver.rb +41 -0
  38. data/test/file_transfer/sender.rb +45 -0
  39. data/test/group_chat/a.rb +37 -0
  40. data/test/group_chat/b.rb +37 -0
  41. data/test/group_chat/c.rb +57 -0
  42. data/torchat.gemspec +21 -0
  43. metadata +140 -0
@@ -0,0 +1,36 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.paranoid.pk | meh@paranoici.org]
3
+ #
4
+ # This file is part of torchat for ruby.
5
+ #
6
+ # torchat for ruby is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # torchat for ruby is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with torchat for ruby. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ class Torchat; module Protocol
21
+
22
+ define_extension :typing do
23
+ define_packet :start do
24
+ define_unpacker_for 0
25
+ end
26
+
27
+ define_packet :thinking do
28
+ define_unpacker_for 0
29
+ end
30
+
31
+ define_packet :stop do
32
+ define_unpacker_for 0
33
+ end
34
+ end
35
+
36
+ end; end
@@ -0,0 +1,603 @@
1
+ #--
2
+ # Copyleft meh. [http://meh.paranoid.pk | meh@paranoici.org]
3
+ #
4
+ # This file is part of torchat for ruby.
5
+ #
6
+ # torchat for ruby is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU Affero General Public License as published
8
+ # by the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # torchat for ruby is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU Affero General Public License for more details.
15
+ #
16
+ # You should have received a copy of the GNU Affero General Public License
17
+ # along with torchat for ruby. If not, see <http://www.gnu.org/licenses/>.
18
+ #++
19
+
20
+ require 'eventmachine'
21
+
22
+ require 'torchat/session/event'
23
+ require 'torchat/session/buddies'
24
+ require 'torchat/session/file_transfers'
25
+ require 'torchat/session/group_chats'
26
+ require 'torchat/session/broadcasts'
27
+
28
+ class Torchat
29
+
30
+ class Session
31
+ attr_reader :config, :id, :name, :description, :status, :buddies, :file_transfers, :group_chats, :broadcasts
32
+ attr_writer :client, :version
33
+ attr_accessor :connection_timeout
34
+
35
+ def initialize (config)
36
+ @config = config
37
+
38
+ @status = :offline
39
+
40
+ @id = @config['id'][/^(.*?)(\.onion)?$/, 1]
41
+ @name = config['name']
42
+ @description = config['description']
43
+
44
+ @buddies = Buddies.new(self)
45
+ @file_transfers = FileTransfers.new(self)
46
+ @group_chats = GroupChats.new(self)
47
+ @broadcasts = Broadcasts.new(self)
48
+
49
+ @callbacks = Hash.new { |h, k| h[k] = [] }
50
+ @before = Hash.new { |h, k| h[k] = [] }
51
+ @after = Hash.new { |h, k| h[k] = [] }
52
+ @timers = []
53
+
54
+ @connection_timeout = 60
55
+
56
+ on :unknown do |e|
57
+ e.buddy.send_packet :not_implemented, e.line.split(' ').first
58
+ end
59
+
60
+ on :verify do |e|
61
+ # this actually gets executed only if the buddy doesn't exist
62
+ # so we can still check if the buddy is permanent below
63
+ buddies.add_temporary e.buddy
64
+
65
+ e.buddy.send_packet :client, client
66
+ e.buddy.send_packet :version, version
67
+ e.buddy.send_packet :supports, Protocol.extensions.map(&:name)
68
+
69
+ e.buddy.send_packet :profile_name, name if name
70
+ e.buddy.send_packet :profile_text, description if description
71
+
72
+ if e.buddy.permanent?
73
+ e.buddy.send_packet :add_me
74
+ end
75
+
76
+ e.buddy.send_packet :status, status
77
+ end
78
+
79
+ on_packet :supports do |e|
80
+ e.buddy.supports *e.packet.to_a
81
+ end
82
+
83
+ on_packet :status do |e|
84
+ next if e.buddy.ready?
85
+
86
+ e.buddy.ready!
87
+
88
+ fire :ready, buddy: e.buddy
89
+ end
90
+
91
+ on_packet :add_me do |e|
92
+ e.buddy.permanent!
93
+ end
94
+
95
+ on_packet :remove_me do |e|
96
+ buddies.remove e.buddy
97
+
98
+ e.buddy.disconnect
99
+ end
100
+
101
+ on_packet :client do |e|
102
+ e.buddy.client.name = e.packet.to_str
103
+ end
104
+
105
+ on_packet :version do |e|
106
+ e.buddy.client.version = e.packet.to_str
107
+ end
108
+
109
+ on_packet :status do |e|
110
+ old = e.buddy.status
111
+
112
+ if old != e.packet.to_sym
113
+ e.buddy.status = e.packet.to_sym
114
+
115
+ fire :status_change, buddy: e.buddy, old: old, new: e.packet.to_sym
116
+ end
117
+ end
118
+
119
+ on_packet :profile_name do |e|
120
+ e.buddy.name = e.packet.to_str
121
+
122
+ fire :profile_change, buddy: e.buddy, changed: :name
123
+ end
124
+
125
+ on_packet :profile_text do |e|
126
+ e.buddy.description = e.packet.to_str
127
+
128
+ fire :profile_change, buddy: e.buddy, changed: :description
129
+ end
130
+
131
+ on_packet :profile_avatar_alpha do |e|
132
+ e.buddy.avatar.alpha = e.packet.data
133
+ end
134
+
135
+ on_packet :profile_avatar do |e|
136
+ e.buddy.avatar.rgb = e.packet.data
137
+
138
+ fire :profile_change, buddy: e.buddy, changed: :avatar
139
+ end
140
+
141
+ on_packet :message do |e|
142
+ fire :message, buddy: e.buddy, message: e.packet.to_str
143
+ end
144
+
145
+ on_packet :filename do |e|
146
+ file_transfers.receive(e.packet.id, e.packet.name, e.packet.size, e.buddy)
147
+ end
148
+
149
+ on_packet :filedata do |e|
150
+ next unless file_transfer = file_transfers[e.packet.id]
151
+
152
+ file_transfer.add_block(e.packet.offset, e.packet.data, e.packet.md5).valid?
153
+ end
154
+
155
+ on_packet :filedata_ok do |e|
156
+ next unless file_transfer = file_transfers[e.packet.id]
157
+
158
+ file_transfer.send_next_block
159
+ end
160
+
161
+ on_packet :filedata_error do |e|
162
+ next unless file_transfer = file_transfers[e.packet.id]
163
+
164
+ file_transfer.send_last_block
165
+ end
166
+
167
+ on_packet :file_stop_sending do |e|
168
+ next unless file_transfer = file_transfers[e.packet.id]
169
+
170
+ file_transfer.stop(true)
171
+ end
172
+
173
+ on_packet :file_stop_receiving do |e|
174
+ next unless file_transfer = file_transfers[e.packet.id]
175
+
176
+ file_transfer.stop(true)
177
+ end
178
+
179
+ set_interval 120 do
180
+ next unless online?
181
+
182
+ buddies.each_value {|buddy|
183
+ next unless buddy.online?
184
+
185
+ if (Time.new.to_i - buddy.last_received.at.to_i) >= 360
186
+ buddy.disconnect
187
+ else
188
+ buddy.send_packet :status, status
189
+ end
190
+ }
191
+ end
192
+
193
+ set_interval 10 do
194
+ next unless online?
195
+
196
+ buddies.each_value {|buddy|
197
+ next if buddy.online? || buddy.blocked?
198
+
199
+ next if (Time.new.to_i - buddy.last_try.to_i) < ((buddy.tries > 36 ? 36 : buddy.tries) * 10)
200
+
201
+ buddy.connect
202
+ }
203
+ end
204
+
205
+ # typing extension support
206
+ on_packet :typing_start do |e|
207
+ e.buddy.typing!
208
+ end
209
+
210
+ on_packet :typing_thinking do |e|
211
+ e.buddy.thinking!
212
+ end
213
+
214
+ on_packet :typing_stop do |e|
215
+ e.buddy.not_typing!
216
+ end
217
+
218
+ on_packet :message do |e|
219
+ e.buddy.not_typing!
220
+ end
221
+
222
+ # groupchat extension support
223
+ on_packet :groupchat, :invite do |e|
224
+ next if group_chats.has_key? e.packet.id
225
+
226
+ group_chat = group_chats.create(e.packet.id, false)
227
+ group_chat.invited_by e.buddy
228
+ group_chat.add e.buddy
229
+
230
+ fire :group_chat_invite, group_chat: group_chat, buddy: e.buddy
231
+ end
232
+
233
+ on_packet :groupchat, :is_participating do |e|
234
+ if group_chats[e.packet.id]
235
+ e.buddy.send_packet [:groupchat, :participating]
236
+ else
237
+ e.buddy.send_packet [:groupchat, :not_participating]
238
+ end
239
+ end
240
+
241
+ on_packet :groupchat, :participants do |e|
242
+ next unless group_chat = group_chats[e.packet.id] and !group_chat.joining?
243
+
244
+ participants = group_chat.participants.keys - e.packet.to_a
245
+ participants.reject! { |p| p == id || p == e.buddy.id }
246
+
247
+ e.buddy.send_packet [:groupchat, :participants], group_chat.id, participants
248
+ end
249
+
250
+ on_packet :groupchat, :participants do |e|
251
+ next unless group_chat = group_chats[e.packet.id] and group_chat.joining? and group_chat.invited_by == e.buddy
252
+
253
+ if e.packet.empty?
254
+ group_chat.joined!
255
+
256
+ next
257
+ end
258
+
259
+ if e.packet.any? { |id| buddies.has_key?(id) && buddies[id].blocked? }
260
+ group_chat.leave
261
+
262
+ next
263
+ end
264
+
265
+ if e.packet.all? { |id| buddies.has_key?(id) && buddies[id].online? }
266
+ e.packet.each {|id|
267
+ group_chat.add buddies[id]
268
+ }
269
+
270
+ e.buddy.send_packet [:groupchat, :participants], group_chat.id, group_chat.participants.keys
271
+
272
+ next
273
+ end
274
+
275
+ e.packet.each {|p|
276
+ next if (buddy = buddies.add_temporary(p)).online?
277
+
278
+ buddy.when :ready do
279
+ buddy.send_packet [:groupchat, :is_participating], group_chat.id
280
+
281
+ participating = buddy.on_packet :groupchat, :participating do |e|
282
+ next if group_chat.left?
283
+
284
+ group_chat.add e.buddy
285
+
286
+ if e.packet.all? { |id| buddies[id].online? }
287
+ e.buddy.send_packet [:groupchat, :participants], group_chat.id, group_chat.participants.keys
288
+ end
289
+ end
290
+
291
+ not_participating = buddy.on_packet :groupchat, :not_participating do |e|
292
+ group_chat.leave
293
+ end
294
+
295
+ # avoid possible memory leak, I don't do this inside both callbacks
296
+ # because a bad guy could not send either of those packets and there
297
+ # would be a leak anyway
298
+ set_timeout 10 do
299
+ participating.remove!
300
+ not_participating.remove!
301
+ end
302
+
303
+ e.remove!
304
+ end
305
+ }
306
+ end
307
+
308
+ on_packet :groupchat, :invited do |e|
309
+ next unless group_chat = group_chats[e.packet.id]
310
+
311
+ unless (buddy = buddies[e.packet.to_s]) && buddy.online?
312
+ buddy = buddies.add_temporary(e.packet.to_s)
313
+
314
+ buddy.on :ready do |e|
315
+ group_chat.add buddy, e.buddy
316
+
317
+ fire :group_chat_join, group_chat: group_chat, buddy: buddy, invited_by: e.buddy
318
+
319
+ e.remove!
320
+ end
321
+ else
322
+ group_chat.add buddy, e.buddy
323
+
324
+ fire :group_chat_join, group_chat: group_chat, buddy: buddy, invited_by: e.buddy
325
+ end
326
+ end
327
+
328
+ on_packet :groupchat, :join do |e|
329
+ next unless group_chat = group_chats[e.packet.id]
330
+
331
+ group_chat.add e.buddy
332
+
333
+ group_chat.participants.each_value {|participant|
334
+ next if participant.id == e.buddy.id
335
+
336
+ participant.send_packet [:groupchat, :invited], group_chat.id, e.buddy.id
337
+ }
338
+
339
+ fire :group_chat_join, group_chat: group_chat, buddy: e.buddy
340
+ end
341
+
342
+ on_packet :groupchat, :leave do |e|
343
+ next unless group_chat = e.buddy.group_chats[e.packet.id]
344
+
345
+ group_chat.leave e.packet.reason
346
+ end
347
+
348
+ on_packet :groupchat, :message do |e|
349
+ next unless group_chat = group_chats[e.packet.id]
350
+
351
+ fire :group_chat_message, group_chat: group_chat, buddy: e.buddy, message: e.packet.to_str
352
+ end
353
+
354
+ before :disconnect do |e|
355
+ e.buddy.group_chats.each_value {|group_chat|
356
+ group_chat.leave
357
+ }
358
+ end
359
+
360
+ after :group_chat_leave do |e|
361
+ if e.group_chat.empty? && group_chats.has_key?(e.group_chat.id)
362
+ group_chats.destroy e.group_chat.id
363
+ end
364
+ end
365
+
366
+ # broadcast support
367
+ on_packet :broadcast, :message do |e|
368
+ broadcasts.received e.packet.to_str
369
+ end
370
+
371
+ # latency support
372
+ on_packet :latency, :ping do |e|
373
+ e.buddy.send_packet [:latency, :pong], e.packet.id
374
+ end
375
+
376
+ on_packet :latency, :pong do |e|
377
+ e.buddy.latency.pong(e.packet.id)
378
+
379
+ fire :latency, buddy: e.buddy, amount: e.buddy.latency.to_f, id: e.packet.id
380
+ end
381
+
382
+ yield self if block_given?
383
+ end
384
+
385
+ def address
386
+ "#{id}.onion"
387
+ end
388
+
389
+ def client
390
+ @client || 'ruby-torchat'
391
+ end
392
+
393
+ def version
394
+ @version || Torchat.version
395
+ end
396
+
397
+ def tor
398
+ Struct.new(:host, :port).new(
399
+ @config['connection']['outgoing']['host'],
400
+ @config['connection']['outgoing']['port'].to_i
401
+ )
402
+ end
403
+
404
+ def name= (value)
405
+ @name = value
406
+
407
+ buddies.each_value {|buddy|
408
+ next unless buddy.online?
409
+
410
+ buddy.send_packet :profile_name, value
411
+ }
412
+ end
413
+
414
+ def description= (value)
415
+ @description = value
416
+
417
+ buddies.each_value {|buddy|
418
+ next unless buddy.online?
419
+
420
+ buddy.send_packet :profile_text, value
421
+ }
422
+ end
423
+
424
+ def offline?; @status == :offline; end
425
+ def online?; !offline?; end
426
+
427
+ def online!
428
+ return if online?
429
+
430
+ @status = :available
431
+
432
+ buddies.each_value {|buddy|
433
+ buddy.connect
434
+ }
435
+ end
436
+
437
+ def offline!
438
+ return if offline?
439
+
440
+ @status = :offline
441
+
442
+ buddies.each_value {|buddy|
443
+ buddy.disconnect
444
+ }
445
+ end
446
+
447
+ def status= (value)
448
+ if value.to_sym.downcase == :offline
449
+ offline!; return
450
+ end
451
+
452
+ online! if offline?
453
+
454
+ unless Protocol[:status].valid?(value)
455
+ raise ArgumentError, "#{value} is not a valid status"
456
+ end
457
+
458
+ @status = value.to_sym.downcase
459
+
460
+ buddies.each_value {|buddy|
461
+ next unless buddy.online?
462
+
463
+ buddy.send_packet :status, @status
464
+ }
465
+ end
466
+
467
+ def on (what, &block)
468
+ what = what.to_sym.downcase
469
+
470
+ @callbacks[what] << block
471
+
472
+ Event::Removable.new(self, what, &block)
473
+ end
474
+
475
+ def on_packet (*args, &block)
476
+ if args.length == 2
477
+ extension, name = args
478
+ else
479
+ extension, name = nil, args.first
480
+ end
481
+
482
+ if name
483
+ on :packet do |e|
484
+ block.call e if e.packet.type == name && e.packet.extension == extension
485
+ end
486
+ else
487
+ on :packet, &block
488
+ end
489
+ end
490
+
491
+ def before (what = nil, &block)
492
+ what = what.to_sym.downcase if what
493
+
494
+ @before[what] << block
495
+
496
+ Event::Removable.new(self, what, :before, &block)
497
+ end
498
+
499
+ def after (what = nil, &block)
500
+ what = what.to_sym.downcase if what
501
+
502
+ @after[what] << block
503
+
504
+ Event::Removable.new(self, what, :after, &block)
505
+ end
506
+
507
+ def remove_callback (chain = nil, name = nil, block)
508
+ if block.is_a? Event::Removable
509
+ chain = block.chain
510
+ name = block.name
511
+ block = block.block
512
+ end
513
+
514
+ if name && chain
515
+ if chain == :before
516
+ @before[name]
517
+ elsif chain == :after
518
+ @after[name]
519
+ else
520
+ @callbacks[name]
521
+ end.delete(block)
522
+ else
523
+ [@before[nil], @before[name], @callbacks[name], @after[name], @after[nil]].each {|callbacks|
524
+ callbacks.each {|callback|
525
+ if callback == block
526
+ callbacks.delete(callback)
527
+
528
+ return
529
+ end
530
+ }
531
+ }
532
+ end
533
+ end
534
+
535
+ def received_packet (packet)
536
+ fire :packet, packet: packet, buddy: packet.from
537
+ end
538
+
539
+ def fire (name, data = nil, &block)
540
+ name = name.downcase.to_sym
541
+ event = Event.new(self, name, data, &block)
542
+
543
+ [@before[nil], @before[name], @callbacks[name], @after[name], @after[nil]].each {|callbacks|
544
+ callbacks.each {|callback|
545
+ begin
546
+ callback.call event
547
+ rescue => e
548
+ Torchat.debug e
549
+ end
550
+
551
+ if event.remove?
552
+ remove_callback(callback)
553
+ event.removed!
554
+ end
555
+
556
+ return if event.stopped?
557
+ }
558
+ }
559
+ end
560
+
561
+ def start (host = nil, port = nil)
562
+ host ||= @config['connection']['incoming']['host']
563
+ port ||= @config['connection']['incoming']['port'].to_i
564
+
565
+ @signature = EM.start_server host, port, Incoming do |incoming|
566
+ incoming.instance_variable_set :@session, self
567
+ end
568
+ end
569
+
570
+ def stop
571
+ EM.stop_server @signature
572
+
573
+ @timers.each {|timer|
574
+ EM.cancel_timer(timer)
575
+ }
576
+ end
577
+
578
+ def set_timeout (*args, &block)
579
+ EM.schedule {
580
+ EM.add_timer(*args, &block).tap {|timer|
581
+ @timers.push(timer)
582
+ }
583
+ }
584
+ end
585
+
586
+ def set_interval (*args, &block)
587
+ EM.schedule {
588
+ EM.add_periodic_timer(*args, &block).tap {|timer|
589
+ @timers.push(timer)
590
+ }
591
+ }
592
+ end
593
+
594
+ def clear_timeout (what)
595
+ EM.schedule {
596
+ EM.cancel_timer(what)
597
+ }
598
+ end
599
+
600
+ alias clear_interval clear_timeout
601
+ end
602
+
603
+ end