agent_xmpp 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.document +5 -0
  2. data/.gitignore +11 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +417 -0
  5. data/Rakefile +75 -0
  6. data/VERSION +1 -0
  7. data/agent_xmpp.gemspec +144 -0
  8. data/lib/agent_xmpp.rb +22 -0
  9. data/lib/agent_xmpp/admin.rb +113 -0
  10. data/lib/agent_xmpp/client.rb +7 -0
  11. data/lib/agent_xmpp/client/boot.rb +83 -0
  12. data/lib/agent_xmpp/client/client.rb +64 -0
  13. data/lib/agent_xmpp/client/connection.rb +108 -0
  14. data/lib/agent_xmpp/client/controller.rb +394 -0
  15. data/lib/agent_xmpp/client/message_delegate.rb +720 -0
  16. data/lib/agent_xmpp/client/message_pipe.rb +193 -0
  17. data/lib/agent_xmpp/client/response.rb +102 -0
  18. data/lib/agent_xmpp/config.rb +48 -0
  19. data/lib/agent_xmpp/main.rb +175 -0
  20. data/lib/agent_xmpp/models.rb +7 -0
  21. data/lib/agent_xmpp/models/contact.rb +85 -0
  22. data/lib/agent_xmpp/models/message.rb +152 -0
  23. data/lib/agent_xmpp/models/publication.rb +53 -0
  24. data/lib/agent_xmpp/models/roster.rb +107 -0
  25. data/lib/agent_xmpp/models/service.rb +91 -0
  26. data/lib/agent_xmpp/models/subscription.rb +61 -0
  27. data/lib/agent_xmpp/models/table_definitions.rb +107 -0
  28. data/lib/agent_xmpp/patches.rb +7 -0
  29. data/lib/agent_xmpp/patches/array.rb +32 -0
  30. data/lib/agent_xmpp/patches/float.rb +10 -0
  31. data/lib/agent_xmpp/patches/hash.rb +13 -0
  32. data/lib/agent_xmpp/patches/object.rb +15 -0
  33. data/lib/agent_xmpp/patches/rexml.rb +69 -0
  34. data/lib/agent_xmpp/patches/string.rb +15 -0
  35. data/lib/agent_xmpp/xmpp.rb +18 -0
  36. data/lib/agent_xmpp/xmpp/element.rb +158 -0
  37. data/lib/agent_xmpp/xmpp/entry.rb +36 -0
  38. data/lib/agent_xmpp/xmpp/error_response.rb +189 -0
  39. data/lib/agent_xmpp/xmpp/iq.rb +90 -0
  40. data/lib/agent_xmpp/xmpp/iq_command.rb +54 -0
  41. data/lib/agent_xmpp/xmpp/iq_disco.rb +206 -0
  42. data/lib/agent_xmpp/xmpp/iq_pubsub.rb +270 -0
  43. data/lib/agent_xmpp/xmpp/iq_roster.rb +183 -0
  44. data/lib/agent_xmpp/xmpp/iq_version.rb +89 -0
  45. data/lib/agent_xmpp/xmpp/jid.rb +150 -0
  46. data/lib/agent_xmpp/xmpp/message.rb +82 -0
  47. data/lib/agent_xmpp/xmpp/presence.rb +127 -0
  48. data/lib/agent_xmpp/xmpp/sasl.rb +241 -0
  49. data/lib/agent_xmpp/xmpp/stanza.rb +107 -0
  50. data/lib/agent_xmpp/xmpp/x_data.rb +357 -0
  51. data/test/app/app.rb +339 -0
  52. data/test/cases/test_application_message_processing.rb +65 -0
  53. data/test/cases/test_errors.rb +24 -0
  54. data/test/cases/test_presence_management.rb +139 -0
  55. data/test/cases/test_roster_management.rb +214 -0
  56. data/test/cases/test_service_discovery.rb +168 -0
  57. data/test/cases/test_session_management.rb +120 -0
  58. data/test/cases/test_version_discovery.rb +67 -0
  59. data/test/helpers/matchers.rb +23 -0
  60. data/test/helpers/mocks.rb +82 -0
  61. data/test/helpers/test_case_extensions.rb +45 -0
  62. data/test/helpers/test_client.rb +44 -0
  63. data/test/helpers/test_delegate.rb +60 -0
  64. data/test/helpers/test_helper.rb +91 -0
  65. data/test/messages/application_messages.rb +206 -0
  66. data/test/messages/error_messages.rb +35 -0
  67. data/test/messages/presence_messages.rb +66 -0
  68. data/test/messages/roster_messages.rb +126 -0
  69. data/test/messages/service_discovery_messages.rb +201 -0
  70. data/test/messages/session_messages.rb +158 -0
  71. data/test/messages/version_discovery_messages.rb +69 -0
  72. data/test/peer/peer.rb +21 -0
  73. metadata +187 -0
@@ -0,0 +1,193 @@
1
+ ##############################################################################################################
2
+ module AgentXmpp
3
+
4
+ #####-------------------------------------------------------------------------------------------------------
5
+ class MessagePipe
6
+
7
+ #---------------------------------------------------------------------------------------------------------
8
+ attr_reader :connection_status, :delegates, :responder_list, :connection, :stream_features,
9
+ :stream_mechanisms, :responder_list_mutex
10
+ #---------------------------------------------------------------------------------------------------------
11
+ alias_method :send_to_method, :send
12
+ #---------------------------------------------------------------------------------------------------------
13
+
14
+ #.........................................................................................................
15
+ def initialize(connection)
16
+ @connection = connection
17
+ @connection_status = :offline;
18
+ @delegates = [MessageDelegate]
19
+ @responder_list = {}
20
+ @responder_list_mutex = Mutex.new
21
+ end
22
+
23
+ #.........................................................................................................
24
+ def add_delegate(delegate)
25
+ @delegates << delegate unless @delegates.include?(delegate)
26
+ end
27
+
28
+ #.........................................................................................................
29
+ def remove_delegate(delegate)
30
+ @delegates.delete(delegate)
31
+ end
32
+
33
+ #.........................................................................................................
34
+ def delegates_respond_to?(method)
35
+ delegates.inject(0){|r,d| d.respond_to?(method) ? r + 1 : r} > 0
36
+ end
37
+
38
+ #.........................................................................................................
39
+ def broadcast_to_delegates(method, *args)
40
+ delegates.inject([]){|r,d| d.respond_to?(method) ? r.push(d.send(method, *args)) : r}.smash
41
+ end
42
+
43
+ #.........................................................................................................
44
+ def send(data, &blk)
45
+ raise AgentXmppError, 'not connected' unless connected?
46
+ if block_given? and data.kind_of?(Xmpp::Stanza)
47
+ if data.id.nil?
48
+ data.id = Xmpp::IdGenerator.generate_id
49
+ end
50
+ add_to_responder_list(data.id, &blk)
51
+ end
52
+ AgentXmpp.logger.info "SEND: #{data.to_s}"
53
+ Message.update(data)
54
+ @connection.send_data(data.to_s)
55
+ end
56
+
57
+ #.........................................................................................................
58
+ def send_resp(resp)
59
+ [resp].flatten.map {|r| r.kind_of?(AgentXmpp::Response) ? send(r.message, &r.responds_with) : r}
60
+ end
61
+
62
+ #.........................................................................................................
63
+ def connected?
64
+ connection and !connection.error?
65
+ end
66
+
67
+ #.........................................................................................................
68
+ def add_to_responder_list(stanza_id, &blk)
69
+ responder_list_mutex.synchronize do
70
+ @responder_list[stanza_id] = {:blk=>blk, :created_at=>Time.now}
71
+ end
72
+ end
73
+
74
+ #.........................................................................................................
75
+ def remove_from_responder_list(stanza_id)
76
+ if @responder_list[stanza_id]
77
+ responder_list_mutex.synchronize{@responder_list.delete(stanza_id)}
78
+ end
79
+ end
80
+
81
+ #---------------------------------------------------------------------------------------------------------
82
+ # connection callbacks
83
+ #.........................................................................................................
84
+ def receive(stanza)
85
+ AgentXmpp.logger.info "RECV: #{stanza.to_s}"
86
+ result = if stanza.kind_of?(Xmpp::Stanza) and stanza.id and callback_info = responder_list[stanza.id]
87
+ responder_list.delete(stanza.id)
88
+ callback_info[:blk].call(stanza)
89
+ else
90
+ process_stanza(stanza)
91
+ end
92
+ send_resp(result)
93
+ end
94
+
95
+ #.........................................................................................................
96
+ def connection_completed
97
+ Boot.call_if_implemented(:call_after_connected, self)
98
+ broadcast_to_delegates(:on_connect, self)
99
+ init_connection.collect{|m| send(m)}
100
+ end
101
+
102
+ #.........................................................................................................
103
+ def unbind
104
+ @connection_status = :off_line
105
+ broadcast_to_delegates(:on_disconnect, self)
106
+ end
107
+
108
+ #.........................................................................................................
109
+ # private
110
+ #.........................................................................................................
111
+ def process_stanza(stanza)
112
+ case stanza.name
113
+ when 'features'
114
+ set_stream_features_and_mechanisms(stanza)
115
+ if connection_status.eql?(:offline)
116
+ broadcast_to_delegates(:on_preauthenticate_features, self)
117
+ elsif connection_status.eql?(:authenticated)
118
+ broadcast_to_delegates(:on_postauthenticate_features, self)
119
+ end
120
+ when 'stream'
121
+ when 'success'
122
+ if connection_status.eql?(:offline)
123
+ @connection.reset_parser
124
+ @connection_status = :authenticated
125
+ broadcast_to_delegates(:on_authenticate, self)
126
+ init_connection(false)
127
+ end
128
+ when 'failure'
129
+ if connection_status.eql?(:offline)
130
+ @connection.reset_parser
131
+ broadcast_to_delegates(:on_did_not_authenticate, self)
132
+ end
133
+ else
134
+ demux_stanza(stanza)
135
+ end
136
+ end
137
+
138
+ #.........................................................................................................
139
+ def demux_stanza(stanza)
140
+ if stanza.respond_to?(:id) and not Message.find_by_item_id(stanza.id)
141
+ Message.update(stanza)
142
+ meth = 'on_' + if stanza.class.eql?(AgentXmpp::Xmpp::Iq)
143
+ iqclass = if stanza.query
144
+ stanza.query.class
145
+ elsif stanza.command
146
+ stanza.command.class
147
+ else
148
+ nil
149
+ end
150
+ if iqclass
151
+ /.*::Iq(.*)/.match(iqclass.to_s).to_a.last
152
+ else
153
+ 'fail'
154
+ end
155
+ else
156
+ /.*::(.*)/.match(stanza.class.to_s).to_a.last
157
+ end.downcase
158
+ meth += '_' + stanza.type.to_s if stanza.type
159
+ if delegates_respond_to?(meth.to_sym)
160
+ broadcast_to_delegates(meth.to_sym, self, stanza)
161
+ else
162
+ broadcast_to_delegates(:on_unsupported_message, self, stanza)
163
+ end
164
+ end
165
+ end
166
+
167
+ #.........................................................................................................
168
+ def set_stream_features_and_mechanisms(stanza)
169
+ @stream_features, @stream_mechanisms = {}, []
170
+ stanza.elements.each do |e|
171
+ if e.name == 'mechanisms' and e.namespace == 'urn:ietf:params:xml:ns:xmpp-sasl'
172
+ e.each_element('mechanism') {|mech| @stream_mechanisms.push(mech.text)}
173
+ else
174
+ @stream_features[e.name] = e.namespace
175
+ end
176
+ end
177
+ end
178
+
179
+ #.........................................................................................................
180
+ def init_connection(starting = true)
181
+ msg = []
182
+ msg.push(Send("<?xml version='1.0' ?>")) if starting
183
+ msg.push(Send("<stream:stream xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0' to='#{AgentXmpp.jid.domain}'>"))
184
+ end
185
+
186
+ #.........................................................................................................
187
+ private :process_stanza, :demux_stanza, :set_stream_features_and_mechanisms, :init_connection
188
+
189
+ #### MessagePipe
190
+ end
191
+
192
+ #### AgentXmpp
193
+ end
@@ -0,0 +1,102 @@
1
+ ##############################################################################################################
2
+ def Send(msg, &blk)
3
+ AgentXmpp::Response.new(msg, &blk)
4
+ end
5
+
6
+ ##############################################################################################################
7
+ module AgentXmpp
8
+
9
+ #####-------------------------------------------------------------------------------------------------------
10
+ class Response
11
+
12
+ #.........................................................................................................
13
+ attr_reader :text, :message, :responds_with
14
+
15
+ #.........................................................................................................
16
+ def initialize(msg, &blk)
17
+ @message = msg
18
+ @text = msg.to_s
19
+ @responds_with = blk
20
+ end
21
+
22
+ #.........................................................................................................
23
+ def to_s
24
+ text
25
+ end
26
+
27
+ #.........................................................................................................
28
+ def method_missing(meth, *args, &blk)
29
+ text.send(meth, *args, &blk)
30
+ end
31
+
32
+ #### Response
33
+ end
34
+
35
+ #####-------------------------------------------------------------------------------------------------------
36
+ class Error
37
+
38
+ #.........................................................................................................
39
+ attr_reader :error, :args
40
+
41
+ #.........................................................................................................
42
+ def initialize(error, *args)
43
+ @error = error.to_sym
44
+ @args = args
45
+ end
46
+
47
+ #.........................................................................................................
48
+ def responce
49
+ Xmpp::ErrorResponse.send(error, *args)
50
+ end
51
+
52
+ #.........................................................................................................
53
+ def method_missing(meth, *args, &blk)
54
+ message.send(meth, *args, &blk)
55
+ end
56
+
57
+ #### Error
58
+ end
59
+
60
+ #####-------------------------------------------------------------------------------------------------------
61
+ class Delegate
62
+
63
+ #.........................................................................................................
64
+ attr_reader :methods
65
+
66
+ #.........................................................................................................
67
+ def initialize
68
+ @methods = []
69
+ end
70
+
71
+ #.........................................................................................................
72
+ def add_delegate_methods(methods)
73
+ @methods << methods
74
+ end
75
+
76
+ #.........................................................................................................
77
+ def delegate(pipe, delegate)
78
+ methods.each do |meths|
79
+ meths.each do |(meth,blk)|
80
+ delegate.define_meta_class_method(meth) do |*args|
81
+ if res = blk.call(*args)
82
+ pipe.send_resp(res)
83
+ pipe.remove_delegate(delegate)
84
+ end
85
+ end
86
+ end
87
+ pipe.add_delegate(delegate)
88
+ end
89
+ methods.clear
90
+ end
91
+
92
+ #.........................................................................................................
93
+ def method_missing(meth, *args, &blk)
94
+ methods.send(meth, *args, &blk)
95
+ end
96
+
97
+ #### Error
98
+ end
99
+
100
+
101
+ #### AgentXmpp
102
+ end
@@ -0,0 +1,48 @@
1
+ module AgentXmpp
2
+
3
+ #.........................................................................................................
4
+ VERSION = "0.1.0"
5
+ AGENT_XMPP_NAME = 'AgentXMPP'
6
+ OS_VERSION = IO.popen('uname -sr').readlines.first.to_s.strip
7
+ SUBSCRIBE_RETRY_PERIOD = 60
8
+ IDENTITY = {:category => 'client', :name => AGENT_XMPP_NAME, :type => 'bot'}
9
+ FEATURES = ['http://jabber.org/protocol/disco#info',
10
+ 'http://jabber.org/protocol/disco#items',
11
+ 'jabber:iq:version',
12
+ 'jabber:x:data',
13
+ 'http://jabber.org/protocol/commands',
14
+ 'http://jabber.org/protocol/pubsub',
15
+ 'http://jabber.org/protocol/pubsub#publish',
16
+ 'http://jabber.org/protocol/pubsub#subscribe',
17
+ 'http://jabber.org/protocol/pubsub#create-nodes',
18
+ 'http://jabber.org/protocol/pubsub#delete-nodes']
19
+ GARBAGE_COLLECTION_INTERVAL = 86400
20
+ DEFAULT_PUBSUB_CONFIG = {
21
+ :title => 'event',
22
+ :access_model => 'presence',
23
+ :max_items => 20,
24
+ :deliver_notifications => 1,
25
+ :deliver_payloads => 1,
26
+ :persist_items => 1,
27
+ :subscribe => 1,
28
+ :notify_config => 0,
29
+ :notify_delete => 0,
30
+ :notify_retract => 0,
31
+ }
32
+
33
+ #.........................................................................................................
34
+ @app_path = File.expand_path(File.dirname($0))
35
+ @log_file = STDOUT
36
+
37
+ #.........................................................................................................
38
+ class << self
39
+ attr_accessor :config_file, :app_path, :log_file
40
+ def logger; @logger ||= Logger.new(STDOUT); end
41
+ def logger=(logger); @logger = logger; end
42
+ end
43
+
44
+ #.........................................................................................................
45
+ class AgentXmppError < Exception; end
46
+
47
+ end
48
+
@@ -0,0 +1,175 @@
1
+ ##############################################################################################################
2
+ OptionParser.new do |opts|
3
+ opts.banner = 'Usage: agent_xmpp.rb [options]'
4
+ opts.separator ''
5
+ opts.on('-a', '--app_path path', 'absolute path to application') {|a| AgentXmpp.app_path = a}
6
+ opts.on('-l', '--logfile file.log', 'name of logfile') {|f| AgentXmpp.log_file = f}
7
+ opts.on_tail('-h', '--help', 'Show this message') {
8
+ puts opts
9
+ exit
10
+ }
11
+ opts.parse!(ARGV)
12
+ end
13
+
14
+ ##############################################################################################################
15
+ module AgentXmpp
16
+
17
+ #...........................................................................................................
18
+ @config = {}
19
+
20
+ #####-------------------------------------------------------------------------------------------------------
21
+ class << self
22
+
23
+ #.........................................................................................................
24
+ attr_accessor :config
25
+
26
+ #####.....................................................................................................
27
+ # database
28
+ #.........................................................................................................
29
+ def in_memory_db
30
+ @in_memory_db ||= Sequel.sqlite
31
+ end
32
+
33
+ #.........................................................................................................
34
+ def agent_xmpp_db
35
+ @agent_xmpp_db ||= Sequel.sqlite("#{AgentXmpp.app_path}/agent_xmpp.db")
36
+ end
37
+
38
+ #.........................................................................................................
39
+ def version
40
+ @version ||= agent_xmpp_db[:version]
41
+ end
42
+
43
+ #.........................................................................................................
44
+ def publication
45
+ @publication ||= PublishModel.new(config['publish'])
46
+ end
47
+
48
+ #####.....................................................................................................
49
+ # pubsub nodes
50
+ #.........................................................................................................
51
+ def pubsub_root
52
+ @pubsub_root ||= "/home/#{AgentXmpp.jid.domain}"
53
+ end
54
+
55
+ #.........................................................................................................
56
+ def user_pubsub_root
57
+ @user_pubsub_root ||= "#{@pubsub_root}/#{AgentXmpp.jid.node}"
58
+ end
59
+
60
+ #####.....................................................................................................
61
+ # client account configuration
62
+ #.........................................................................................................
63
+ def is_account_jid?(jid)
64
+ @jid.bare.to_s.eql?(bare_jid_to_s(jid))
65
+ end
66
+
67
+ #.........................................................................................................
68
+ def jid
69
+ @jid ||= Xmpp::Jid.new("#{config['jid']}/#{resource}")
70
+ end
71
+
72
+ #.........................................................................................................
73
+ def jid=(jid)
74
+ @jid = jid
75
+ end
76
+
77
+ #.........................................................................................................
78
+ def resource
79
+ config['resource'] || Socket.gethostname
80
+ end
81
+
82
+ #.........................................................................................................
83
+ def port
84
+ config['port'] || 5222
85
+ end
86
+
87
+ #.........................................................................................................
88
+ def password
89
+ config['password']
90
+ end
91
+
92
+ #.........................................................................................................
93
+ def priority
94
+ @priority ||= if config['priority']
95
+ if config['priority'] < -127
96
+ -127
97
+ elsif config['priority'] > 128
98
+ 128
99
+ else
100
+ config['priority']
101
+ end
102
+ else; 1; end
103
+ end
104
+
105
+ #.........................................................................................................
106
+ def bare_jid_to_s(jid)
107
+ case jid
108
+ when String then Xmpp::Jid.new(jid).bare.to_s
109
+ when Xmpp::Jid then jid.bare.to_s
110
+ else jid
111
+ end
112
+ end
113
+
114
+ #.........................................................................................................
115
+ def full_jid_to_s(jid)
116
+ case jid
117
+ when String then jid
118
+ when Xmpp::Jid then jid.to_s
119
+ else jid
120
+ end
121
+ end
122
+
123
+ #.........................................................................................................
124
+ def start_garbage_collection(pipe)
125
+ EventMachine::PeriodicTimer.new(AgentXmpp::GARBAGE_COLLECTION_INTERVAL) do
126
+ AgentXmpp.logger.info "GARBAGE COLLECTION IN PROGRESS ON INTERVAL: #{AgentXmpp::GARBAGE_COLLECTION_INTERVAL}"
127
+ AgentXmpp::BaseController.commands_list.each do |(session, command_info)|
128
+ AgentXmpp::BaseController.remove_command_from_list(session) if Time.now - command_info[:created_at] > AgentXmpp::GARBAGE_COLLECTION_INTERVAL
129
+ end
130
+ pipe.responder_list.each do |(stanza_id, command_info)|
131
+ pipe.remove_from_responder_list(stanza_id) if Time.now - command_info[:created_at] > AgentXmpp::GARBAGE_COLLECTION_INTERVAL
132
+ end
133
+ end
134
+ end
135
+
136
+ #### self
137
+ end
138
+
139
+ #####-------------------------------------------------------------------------------------------------------
140
+ module Delegator
141
+
142
+ #####-------------------------------------------------------------------------------------------------------
143
+ class << self
144
+
145
+ #.........................................................................................................
146
+ def delegate(del, *methods)
147
+ methods.each do |method_name|
148
+ class_eval <<-RUBY
149
+ def #{method_name.to_s}(*args, &blk)
150
+ ::#{del}.send(#{method_name.inspect}, *args, &blk)
151
+ end
152
+ RUBY
153
+ end
154
+ end
155
+
156
+ #### self
157
+ end
158
+
159
+ delegate AgentXmpp::BaseController, :command, :chat, :event, :before, :include_module
160
+ delegate AgentXmpp::Boot, :before_start, :after_connected, :restarting_client, :discovered_pubsub_node,
161
+ :discovered_command_nodes, :received_presence
162
+
163
+ #### Delegator
164
+ end
165
+
166
+ #### AgentXmpp
167
+ end
168
+
169
+ ##############################################################################################################
170
+ include AgentXmpp::Delegator
171
+
172
+ ##############################################################################################################
173
+ at_exit do
174
+ AgentXmpp::Boot.boot
175
+ end