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,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