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,89 @@
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 'stringio'
21
+
22
+ require 'torchat/session/file_transfer'
23
+
24
+ class Torchat; class Session
25
+
26
+ class FileTransfers < Hash
27
+ attr_reader :session
28
+
29
+ def initialize (session)
30
+ @session = session
31
+ end
32
+
33
+ def receive (id, name, size, sender = nil)
34
+ if has_key? id
35
+ raise ArgumentError, 'file transfer with same id already exists'
36
+ end
37
+
38
+ FileTransfer.new(id, name, size).tap {|ft|
39
+ ft.from = sender
40
+
41
+ self[ft.id] = ft
42
+
43
+ fire :file_transfer_start, file_transfer: ft
44
+ }
45
+ end
46
+
47
+ def send_file (buddy, path)
48
+ unless File.readable?(path)
49
+ raise ArgumentError, "#{path} is unreadable"
50
+ end
51
+
52
+ FileTransfer.new(self, File.basename(path), File.size(path)).tap {|ft|
53
+ ft.to = buddy
54
+ ft.input = File.open(path)
55
+
56
+ start_sending(self[ft.id] = ft)
57
+ }
58
+ end
59
+
60
+ def send_blob (buddy, data, name = Torchat.new_cookie)
61
+ self[id] = FileTransfer.new(self, name, data.size).tap {|ft|
62
+ ft.to = buddy
63
+ ft.input = StringIO.new(data)
64
+
65
+ start_sending(self[ft.id] = ft)
66
+ }
67
+ end
68
+
69
+ def stop (id)
70
+ unless file_transfer = self[id]
71
+ raise ArgumentError, 'unexistent file transfer'
72
+ end
73
+
74
+ delete(file_transfer.stop.id)
75
+ end
76
+
77
+ private
78
+ def start_sending (file_transfer)
79
+ session.fire :file_transfer_start, file_transfer: file_transfer
80
+
81
+ file_transfer.to.send_packet :filename, file_transfer.name, file_transfer.size, file_transfer.block_size, file_transfer.id
82
+
83
+ file_transfer.next_block.tap {|block|
84
+ file_transfer.to.send_packet :filedata, file_transfer.id, block.offset, block.data, block.md5
85
+ }
86
+ end
87
+ end
88
+
89
+ end; end
@@ -0,0 +1,123 @@
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 'forwardable'
21
+
22
+ class Torchat; class Session
23
+
24
+ class GroupChat
25
+ attr_reader :session, :id, :modes, :participants
26
+
27
+ def initialize (session, id, *modes)
28
+ @session = session
29
+ @id = id
30
+ @modes = modes.flatten.uniq.compact.map(&:to_sym)
31
+
32
+ @participants = Participants.new(self)
33
+ @joining = true
34
+ end
35
+
36
+ def method_missing (id, *args, &block)
37
+ return @participants.__send__ id, *args, &block if @participants.respond_to? id
38
+
39
+ super
40
+ end
41
+
42
+ def respond_to_missing? (id, include_private = false)
43
+ @participants.respond_to? id, include_private
44
+ end
45
+
46
+ def joining?; @joining; end
47
+ def joined?; !joining?; end
48
+
49
+ def joined!
50
+ @joining = false
51
+
52
+ if invited_by
53
+ invited_by.send_packet [:groupchat, :join], id
54
+
55
+ session.fire :group_chat_join, group_chat: self, invited_by: invited_by
56
+ end
57
+
58
+ participants.each_value {|participant|
59
+ session.fire :group_chat_join, group_chat: self, buddy: ~participant
60
+ }
61
+ end
62
+
63
+ def on (what, &block)
64
+ session.on what do |e|
65
+ block.call e if e.group_chat == self
66
+ end
67
+ end
68
+
69
+ def invited_by (value = nil)
70
+ raise ArgumentError, 'cannot overwrite the invitor' if @invited_by && value
71
+
72
+ value ? @invited_by = value : @invited_by
73
+ end
74
+
75
+ def invited?
76
+ !!invited_by
77
+ end
78
+
79
+ def invite (buddy)
80
+ return unless buddy = session.buddies[buddy]
81
+
82
+ return unless buddy.online?
83
+
84
+ return if participants.has_key? buddy.id
85
+
86
+ unless buddy.supports? :groupchat
87
+ raise ArgumentError, "#{buddy.id} doesn't support groupchats"
88
+ end
89
+
90
+ buddy.send_packet [:groupchat, :invite], id, modes
91
+ buddy.send_packet [:groupchat, :participants], id, participants.keys
92
+
93
+ self
94
+ end
95
+
96
+ def left?; @left; end
97
+
98
+ def leave (reason = nil)
99
+ return if left?
100
+
101
+ @left = true
102
+
103
+ participants.each_value {|buddy|
104
+ buddy.send_packet [:groupchat, :leave], id, reason
105
+ }
106
+
107
+ session.group_chats.destroy id
108
+ end
109
+
110
+ def send_message (message)
111
+ participants.each_value {|buddy|
112
+ buddy.send_packet [:groupchat, :message], id, message
113
+ }
114
+ end
115
+
116
+ def inspect
117
+ "#<Torchat::GroupChat(#{id})#{"[#{modes.join ' '}]" unless modes.empty?}: #{empty? ? 'empty' : keys.join(' ')}>"
118
+ end
119
+ end
120
+
121
+ end; end
122
+
123
+ require 'torchat/session/group_chat/participants'
@@ -0,0 +1,38 @@
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 'delegate'
21
+
22
+ require 'torchat/session/buddy'
23
+
24
+ class Torchat; class Session; class GroupChat
25
+
26
+ class Participant < DelegateClass(Buddy)
27
+ attr_reader :invited_by
28
+
29
+ def initialize (buddy, invited_by = nil)
30
+ super(buddy)
31
+
32
+ @invited_by = invited_by
33
+ end
34
+
35
+ alias ~ __getobj__
36
+ end
37
+
38
+ end; end; end
@@ -0,0 +1,52 @@
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 'torchat/session/group_chat/participant'
21
+
22
+ class Torchat; class Session; class GroupChat
23
+
24
+ class Participants < Hash
25
+ attr_reader :group_chat
26
+
27
+ def initialize (group_chat)
28
+ @group_chat = group_chat
29
+ end
30
+
31
+ def add (id, invited_by = nil)
32
+ return if has_key? Torchat.normalize_id(id)
33
+
34
+ if buddy = group_chat.session.buddies[id]
35
+ buddy.group_chats.add group_chat
36
+
37
+ self[buddy.id] = Participant.new(buddy, invited_by)
38
+ end
39
+ end
40
+
41
+ def delete (id)
42
+ super(Torchat.normalize_id(id))
43
+
44
+ if buddy = group_chat.session.buddies[id]
45
+ buddy.group_chats.delete(group_chat)
46
+ end
47
+ end
48
+
49
+ private :[]=
50
+ end
51
+
52
+ end; end; end
@@ -0,0 +1,74 @@
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 'torchat/session/group_chat'
21
+
22
+ class Torchat; class Session
23
+
24
+ class GroupChats < Hash
25
+ attr_reader :session
26
+
27
+ def initialize (session)
28
+ @session = session
29
+ end
30
+
31
+ def has_key? (name)
32
+ name = name.id if name.is_a? GroupChat
33
+
34
+ !!self[name]
35
+ end
36
+
37
+ def [] (name)
38
+ name = name.id if name.is_a? GroupChat
39
+
40
+ super(name)
41
+ end
42
+
43
+ private :[]=
44
+
45
+ def create (id = Torchat.new_cookie, joined = true)
46
+ if has_key? id
47
+ raise ArgumentError, "a groupchat named #{id} already exists"
48
+ end
49
+
50
+ GroupChat.new(session, id).tap {|group_chat|
51
+ group_chat.joined! if joined
52
+
53
+ self[id] = group_chat
54
+
55
+ session.fire :group_chat_create, group_chat: group_chat
56
+ }
57
+ end
58
+
59
+ def destroy (id)
60
+ unless has_key? id
61
+ raise ArgumentError, "a groupchat named #{id} doesn't exists"
62
+ end
63
+
64
+ self[id].tap {|group_chat|
65
+ delete group_chat.id
66
+
67
+ session.fire :group_chat_destroy, group_chat: group_chat
68
+ }
69
+ end
70
+
71
+ private :delete
72
+ end
73
+
74
+ end; end
@@ -0,0 +1,187 @@
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; class Session
21
+
22
+ class Incoming < EventMachine::Protocols::LineAndTextProtocol
23
+ attr_accessor :owner
24
+
25
+ def post_init
26
+ @delayed = []
27
+ end
28
+
29
+ def receive_line (line)
30
+ packet = begin
31
+ Protocol.unpack(line.chomp, @owner)
32
+ rescue => e
33
+ if e.is_a?(ArgumentError) && e.message.end_with?('packet unknown')
34
+ @session.fire :unknown, line: line, buddy: @owner
35
+ else
36
+ Torchat.debug line.inspect
37
+ Torchat.debug e
38
+ end
39
+
40
+ return
41
+ end
42
+
43
+ if packet.type == :ping && !packet.extension
44
+ Torchat.debug "ping incoming from claimed #{packet.id}", level: 2
45
+
46
+ if @session.offline?
47
+ close_connection_after_writing
48
+
49
+ Torchat.debug 'we are offline, kill the connection'
50
+
51
+ return
52
+ end
53
+
54
+ if !packet.valid? || packet.id == @session.id || @last_ping && packet.id != @last_ping.id
55
+ close_connection_after_writing
56
+
57
+ Torchat.debug 'invalid packet or DoS attempt'
58
+
59
+ return
60
+ end
61
+
62
+ @last_ping = packet
63
+
64
+ if buddy = @session.buddies[packet.id]
65
+ if buddy.blocked?
66
+ close_connection_after_writing
67
+
68
+ Torchat.debug "#{buddy.id} is blocked"
69
+
70
+ return
71
+ end
72
+
73
+ if buddy.has_incoming? && buddy.instance_variable_get(:@incoming) != self
74
+ close_connection_after_writing
75
+
76
+ Torchat.debug "#{buddy.id} already has an incoming connection"
77
+
78
+ return
79
+ end
80
+ end
81
+
82
+ if @owner
83
+ @owner.send_packet :pong, packet.cookie
84
+ else
85
+ if buddy
86
+ buddy.last_received = packet
87
+
88
+ buddy.connect
89
+
90
+ if buddy.connected?
91
+ buddy.send_packet! :pong, packet.cookie
92
+ else
93
+ buddy.send_packet :pong, packet.cookie
94
+ end
95
+ else
96
+ @temp_buddy = Buddy.new(@session, packet.id, self)
97
+
98
+ @temp_buddy.last_received = packet
99
+
100
+ @temp_buddy.connect
101
+ @temp_buddy.send_packet :pong, packet.cookie
102
+ end
103
+ end
104
+ elsif packet.type == :pong && !packet.extension
105
+ unless buddy = (@session.buddies[(@last_ping.id rescue nil)] || @temp_buddy) || !buddy.pinged?
106
+ close_connection_after_writing
107
+
108
+ Torchat.debug 'pong received without a ping'
109
+
110
+ return
111
+ end
112
+
113
+ if buddy.blocked?
114
+ close_connection_after_writing
115
+
116
+ Torchat.debug "#{buddy.id} is blocked."
117
+
118
+ return
119
+ end
120
+
121
+ buddy.last_received = packet
122
+
123
+ return unless @last_ping
124
+
125
+ if packet.cookie != buddy.pinged?
126
+ close_connection_after_writing
127
+
128
+ Torchat.debug "#{"#{packet.from.id} sent " if packet.from}pong with wrong cookie, got #{packet.cookie.inspect} expected #{buddy.pinged?.inspect}"
129
+
130
+ return
131
+ end
132
+
133
+ unless buddy.verified?
134
+ buddy.verified self
135
+ end
136
+
137
+ buddy.pong!
138
+ else
139
+ unless @owner
140
+ if @last_ping
141
+ if @delayed.length > 23
142
+ close_connection_after_writing
143
+
144
+ Torchat.debug 'too many cached packets'
145
+ else
146
+ @delayed << packet
147
+ end
148
+ else
149
+ close_connection_after_writing
150
+
151
+ Torchat.debug 'someone sent a packet before the handshake'
152
+ Torchat.debug "the packet was #{packet.inspect}", level: 2
153
+ end
154
+
155
+ return
156
+ end
157
+
158
+ Torchat.debug "<< #{@owner ? @owner.id : 'unknown'} #{packet.inspect}", level: 2
159
+
160
+ @owner.last_received = packet
161
+
162
+ @owner.session.received_packet packet
163
+ end
164
+ rescue => e
165
+ Torchat.debug e
166
+ end
167
+
168
+ def verification_completed
169
+ @delayed.each {|packet|
170
+ packet.from = @owner
171
+
172
+ @owner.session.received_packet packet
173
+ }
174
+
175
+ @delayed = nil
176
+ end
177
+
178
+ def unbind
179
+ if error?
180
+ Torchat.debug "errno #{EM.report_connection_error_status(@signature)}", level: 2
181
+ end
182
+
183
+ @owner.disconnect if @owner
184
+ end
185
+ end
186
+
187
+ end; end