agent_xmpp 0.1.0

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