Project

General

Profile

« Previous | Next » 

Revision 7e9520c0

Added by Marc Dequènes almost 11 years ago

  • ID 7e9520c0a06d4516ffef0594c6e2a2a8b708edbc

[evol] conversation/bot protocol rework §7 (refs #30)

View differences:

lib/cyborghood/cyborg/conversation.rb
18 18

  
19 19
require 'yaml'
20 20
require 'cyborghood/cyborg/session'
21
require 'cyborghood/cyborg/protocol'
21 22

  
22
# Note: Event machine core is mono-thread, so this code is not threadsafe.
23
#       Only long tasks will be run in threads, which do not care about all this.
24 23

  
25 24
module CyborgHood
26

  
27
  module BotProtocol
28
    VERSION = "0.1"
29
    CAPABILITIES = []
30

  
31
    @@request_callback = proc{|result| process_request_result(result) }
32

  
33
    # TODO:
34
    #   - check for request/reply couples (reply to wrong of non-existent request)
35
    #   - check for negociation wip/done
36

  
37
    def initialize(conversation)
38
      @conversation = conversation
39

  
40
      @negociation_received = false
41
      @negociation_sent = false
42
      @negociation_ok = false
43
    end
44

  
45
    def negociation_ok?
46
      @negociation_ok
47
    end
48

  
49
    def process_received_message(message)
50
      method = "receive_" + message.action_code.downcase.tr(" ", "_")
51
      if respond_to? method
52
        send(method, message)
53
      else
54
        send_error_protocol "unknown action"
55
      end
56
    end
57

  
58
    def process_request_result(result)
59
      if result[:error]
60
        send_error_action(result[:reply_message], result[:error])
61
      else
62
        send_reply_result(result[:reply_message], result[:action_result])
63
      end
64
    end
65

  
66
    def receive_announce_helo(message)
67
      unless message.conv_thread.id == 0
68
        return send_quit_decline "bad negociation"
69
      end
70
      unless message.parameters[:bot_name] =~ Conversation::BOT_ID_PATTERN
71
        return send_quit_decline "bad bot name"
72
      end
73
      unless message.parameters[:protocol_version] == VERSION
74
        return send_quit_decline "protocol version does not match"
75
      end
76
      @negociation_received = true
77
      @conversation.set_peer_info(message.parameters[:bot_name], message.parameters[:capabilities])
78

  
79
      if @negociation_sent
80
        send_announce_ok(message)
81
        @negociation_ok = true
82
      else
83
        send_announce_helo(message)
84
        @negociation_sent = true
85
      end
86
    end
87

  
88
    def receive_announce_ok(message)
89
      unless @negociation_sent and @negociation_received
90
        send_quit_decline "bad negociation"
91
      end
92
      @negociation_ok = true
93
    end
94

  
95
    def receive_request_capabilities(message)
96
      send_reply_result(message, @conversation.capabilities + CAPABILITIES)
97
    end
98

  
99
    def receive_request_call(message)
100
      unless @conversation.bot.interface.is_node? message.parameters[:node]
101
        return send_error_action(message, "bad node")
102
      end
103
      send_reply_ack(message)
104
      @conversation.bot.schedule_task(@@request_callback) do
105
        result = {
106
          :reply_message => message
107
        }
108
        begin
109
          result[:action_result] = @conversation.bot.interface.call(message.conv_thread.session,
110
                                                                    message.parameters[:node],
111
                                                                    message.parameters[:data])
112
        rescue
113
          result[:error] = $!
114
        end
115
      end
116
    end
117

  
118
    def receive_request_exists(message)
119
      unless @conversation.bot.interface.is_node? message.parameters[:node]
120
        return send_error_action(message, "bad node")
121
      end
122
      send_reply_ack(message)
123
      @conversation.bot.schedule_task(@@request_callback) do
124
        {
125
          :reply_message => message,
126
          :action_result => @conversation.bot.interface.has_node? message.parameters[:node]
127
        }
128
      end
129
    end
130

  
131
    def receive_request_describe(message)
132
      # TODO: implement when ready in the interface
133
      send_quit_decline(message, "not implemented")
134
    end
135

  
136
    def send_announce_helo(recv_message = nil)
137
      action_code = "ANNOUNCE HELO"
138
      message = (recv_message.nil? ? @conversation.thread('system').new_message(action_code) :
139
                 recv_message.create_reply(action_code))
140
      message.send
141
    end
142

  
143
    def send_announce_ok(recv_message)
144
      recv_message.create_reply("ANNOUNCE OK").send
145
    end
146

  
147
    def send_request_capabilities
148
      @conversation.thread('system').new_message("REQUEST EXISTS", { :node => node }).send
149
    end
150

  
151
    def send_request_call(conv_thread, node)
152
      conv_thread.new_message("REQUEST CALL", { :node => node }).send
153
    end
154

  
155
    def send_request_exists(conv_thread, node)
156
      conv_thread.new_message("REQUEST EXISTS", { :node => node }).send
157
    end
158

  
159
    def send_request_describe(conv_thread, node)
160
      conv_thread.new_message("REQUEST DESCRIBE", { :node => node }).send
161
    end
162

  
163
    def send_error_protocol(error, fatal = false)
164
      @conversation.thread('system').new_message("ERROR PROTOCOL", { :error => error }).send
165
      @conversation.set_error_status(fatal)
166
    end
167

  
168
    def send_error_action(recv_message, error)
169
      recv_message.create_reply("ERROR ACTION", { :error => error }).send
170
    end
171

  
172
    def send_reply_ack(recv_message)
173
      recv_message.create_reply("REPLY ACK").send
174
    end
175

  
176
    def send_reply_decline(recv_message, reason)
177
      recv_message.create_reply("REPLY DECLINE", { :reason => reason }).send
178
    end
179

  
180
    def send_reply_result(recv_message, result)
181
      recv_message.create_reply("REPLY ACK", { :result => result }).send
182
    end
183

  
184
    def send_quit_decline(reason)
185
      @conversation.thread('system').new_message("QUIT LEAVING", { :reason => reason }).send
186
    end
187

  
188
    def send_quit_leaving
189
      @conversation.thread('system').new_message("QUIT LEAVING").send
190
    end
191
  end
192

  
193 25
  class ConversationThread
194 26
    attr_reader :conversation, :name, :id, :session
195 27

  
lib/cyborghood/cyborg/protocol.rb
1
#--
2
# CyborgHood, a distributed system management software.
3
# Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
4
#
5
# This program is free software: you can redistribute it and/or modify
6
# it under the terms of the GNU General Public License as published by
7
# the Free Software Foundation, either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# This program is distributed in the hope that it will be useful,
11
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
#++
18

  
19
# Note: Event machine core is mono-thread, so this code is not threadsafe.
20
#       Only long tasks will be run in threads, which do not care about all this.
21

  
22

  
23
module CyborgHood
24
  module BotProtocol
25
    VERSION = "0.1"
26
    CAPABILITIES = []
27

  
28
    @@request_callback = proc{|result| process_request_result(result) }
29

  
30
    # TODO:
31
    #   - check for request/reply couples (reply to wrong of non-existent request)
32

  
33
    def initialize(conversation)
34
      @conversation = conversation
35

  
36
      @negociation_received = false
37
      @negociation_sent = false
38
      @negociation_ok = false
39
    end
40

  
41
    def self.process_request_result(result)
42
      if result[:error]
43
        send_error_action(result[:reply_message], result[:error])
44
      else
45
        send_reply_result(result[:reply_message], result[:action_result])
46
      end
47
    end
48

  
49
    def negociation_ok?
50
      @negociation_ok
51
    end
52

  
53
    def process_received_message(message)
54
      method = "receive_" + message.action_code.downcase.tr(" ", "_")
55
      if respond_to? method
56
        send(method, message)
57
      else
58
        send_error_protocol "unknown action"
59
      end
60
    end
61

  
62
    def receive_announce_helo(message)
63
      unless message.conv_thread.id == 0
64
        return send_quit_decline "bad negociation"
65
      end
66
      unless message.parameters[:bot_name] =~ Conversation::BOT_ID_PATTERN
67
        return send_quit_decline "bad bot name"
68
      end
69
      unless message.parameters[:protocol_version] == VERSION
70
        return send_quit_decline "protocol version does not match"
71
      end
72
      @negociation_received = true
73
      @conversation.set_peer_info(message.parameters[:bot_name], message.parameters[:capabilities])
74

  
75
      if @negociation_sent
76
        send_announce_ok(message)
77
        @negociation_ok = true
78
      else
79
        send_announce_helo(message)
80
        @negociation_sent = true
81
      end
82
    end
83

  
84
    def receive_announce_ok(message)
85
      unless @negociation_sent and @negociation_received
86
        send_quit_decline "bad negociation"
87
      end
88
      @negociation_ok = true
89
    end
90

  
91
    def receive_request_capabilities(message)
92
      send_reply_result(message, @conversation.capabilities + CAPABILITIES)
93
    end
94

  
95
    def receive_request_call(message)
96
      unless @conversation.bot.interface.is_node? message.parameters[:node]
97
        return send_error_action(message, "bad node")
98
      end
99
      send_reply_ack(message)
100
      @conversation.bot.schedule_task(@@request_callback) do
101
        result = {
102
          :reply_message => message
103
        }
104
        begin
105
          result[:action_result] = @conversation.bot.interface.call(message.conv_thread.session,
106
                                                                    message.parameters[:node],
107
                                                                    message.parameters[:data])
108
        rescue
109
          result[:error] = $!
110
        end
111
      end
112
    end
113

  
114
    def receive_request_exists(message)
115
      unless @conversation.bot.interface.is_node? message.parameters[:node]
116
        return send_error_action(message, "bad node")
117
      end
118
      send_reply_ack(message)
119
      @conversation.bot.schedule_task(@@request_callback) do
120
        {
121
          :reply_message => message,
122
          :action_result => @conversation.bot.interface.has_node? message.parameters[:node]
123
        }
124
      end
125
    end
126

  
127
    def receive_request_describe(message)
128
      # TODO: implement when ready in the interface
129
      send_quit_decline(message, "not implemented")
130
    end
131

  
132
    def send_announce_helo(recv_message = nil)
133
      action_code = "ANNOUNCE HELO"
134
      message = (recv_message.nil? ? @conversation.thread('system').new_message(action_code) :
135
                 recv_message.create_reply(action_code))
136
      message.send
137
    end
138

  
139
    def send_announce_ok(recv_message)
140
      recv_message.create_reply("ANNOUNCE OK").send
141
    end
142

  
143
    def send_request_capabilities
144
      @conversation.thread('system').new_message("REQUEST EXISTS", { :node => node }).send
145
    end
146

  
147
    def send_request_call(conv_thread, node)
148
      conv_thread.new_message("REQUEST CALL", { :node => node }).send
149
    end
150

  
151
    def send_request_exists(conv_thread, node)
152
      conv_thread.new_message("REQUEST EXISTS", { :node => node }).send
153
    end
154

  
155
    def send_request_describe(conv_thread, node)
156
      conv_thread.new_message("REQUEST DESCRIBE", { :node => node }).send
157
    end
158

  
159
    def send_error_protocol(error, fatal = false)
160
      @conversation.thread('system').new_message("ERROR PROTOCOL", { :error => error }).send
161
      @conversation.set_error_status(fatal)
162
    end
163

  
164
    def send_error_action(recv_message, error)
165
      recv_message.create_reply("ERROR ACTION", { :error => error }).send
166
    end
167

  
168
    def send_reply_ack(recv_message)
169
      recv_message.create_reply("REPLY ACK").send
170
    end
171

  
172
    def send_reply_decline(recv_message, reason)
173
      recv_message.create_reply("REPLY DECLINE", { :reason => reason }).send
174
    end
175

  
176
    def send_reply_result(recv_message, result)
177
      recv_message.create_reply("REPLY ACK", { :result => result }).send
178
    end
179

  
180
    def send_quit_decline(reason)
181
      @conversation.thread('system').new_message("QUIT LEAVING", { :reason => reason }).send
182
    end
183

  
184
    def send_quit_leaving
185
      @conversation.thread('system').new_message("QUIT LEAVING").send
186
    end
187
  end
188
end

Also available in: Unified diff