torchat 0.0.1.rc.1

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