Revision 7e9520c0
Added by Marc Dequènes about 14 years ago
- ID 7e9520c0a06d4516ffef0594c6e2a2a8b708edbc
lib/cyborghood/cyborg/conversation.rb | ||
---|---|---|
|
||
require 'yaml'
|
||
require 'cyborghood/cyborg/session'
|
||
require 'cyborghood/cyborg/protocol'
|
||
|
||
# Note: Event machine core is mono-thread, so this code is not threadsafe.
|
||
# Only long tasks will be run in threads, which do not care about all this.
|
||
|
||
module CyborgHood
|
||
|
||
module BotProtocol
|
||
VERSION = "0.1"
|
||
CAPABILITIES = []
|
||
|
||
@@request_callback = proc{|result| process_request_result(result) }
|
||
|
||
# TODO:
|
||
# - check for request/reply couples (reply to wrong of non-existent request)
|
||
# - check for negociation wip/done
|
||
|
||
def initialize(conversation)
|
||
@conversation = conversation
|
||
|
||
@negociation_received = false
|
||
@negociation_sent = false
|
||
@negociation_ok = false
|
||
end
|
||
|
||
def negociation_ok?
|
||
@negociation_ok
|
||
end
|
||
|
||
def process_received_message(message)
|
||
method = "receive_" + message.action_code.downcase.tr(" ", "_")
|
||
if respond_to? method
|
||
send(method, message)
|
||
else
|
||
send_error_protocol "unknown action"
|
||
end
|
||
end
|
||
|
||
def process_request_result(result)
|
||
if result[:error]
|
||
send_error_action(result[:reply_message], result[:error])
|
||
else
|
||
send_reply_result(result[:reply_message], result[:action_result])
|
||
end
|
||
end
|
||
|
||
def receive_announce_helo(message)
|
||
unless message.conv_thread.id == 0
|
||
return send_quit_decline "bad negociation"
|
||
end
|
||
unless message.parameters[:bot_name] =~ Conversation::BOT_ID_PATTERN
|
||
return send_quit_decline "bad bot name"
|
||
end
|
||
unless message.parameters[:protocol_version] == VERSION
|
||
return send_quit_decline "protocol version does not match"
|
||
end
|
||
@negociation_received = true
|
||
@conversation.set_peer_info(message.parameters[:bot_name], message.parameters[:capabilities])
|
||
|
||
if @negociation_sent
|
||
send_announce_ok(message)
|
||
@negociation_ok = true
|
||
else
|
||
send_announce_helo(message)
|
||
@negociation_sent = true
|
||
end
|
||
end
|
||
|
||
def receive_announce_ok(message)
|
||
unless @negociation_sent and @negociation_received
|
||
send_quit_decline "bad negociation"
|
||
end
|
||
@negociation_ok = true
|
||
end
|
||
|
||
def receive_request_capabilities(message)
|
||
send_reply_result(message, @conversation.capabilities + CAPABILITIES)
|
||
end
|
||
|
||
def receive_request_call(message)
|
||
unless @conversation.bot.interface.is_node? message.parameters[:node]
|
||
return send_error_action(message, "bad node")
|
||
end
|
||
send_reply_ack(message)
|
||
@conversation.bot.schedule_task(@@request_callback) do
|
||
result = {
|
||
:reply_message => message
|
||
}
|
||
begin
|
||
result[:action_result] = @conversation.bot.interface.call(message.conv_thread.session,
|
||
message.parameters[:node],
|
||
message.parameters[:data])
|
||
rescue
|
||
result[:error] = $!
|
||
end
|
||
end
|
||
end
|
||
|
||
def receive_request_exists(message)
|
||
unless @conversation.bot.interface.is_node? message.parameters[:node]
|
||
return send_error_action(message, "bad node")
|
||
end
|
||
send_reply_ack(message)
|
||
@conversation.bot.schedule_task(@@request_callback) do
|
||
{
|
||
:reply_message => message,
|
||
:action_result => @conversation.bot.interface.has_node? message.parameters[:node]
|
||
}
|
||
end
|
||
end
|
||
|
||
def receive_request_describe(message)
|
||
# TODO: implement when ready in the interface
|
||
send_quit_decline(message, "not implemented")
|
||
end
|
||
|
||
def send_announce_helo(recv_message = nil)
|
||
action_code = "ANNOUNCE HELO"
|
||
message = (recv_message.nil? ? @conversation.thread('system').new_message(action_code) :
|
||
recv_message.create_reply(action_code))
|
||
message.send
|
||
end
|
||
|
||
def send_announce_ok(recv_message)
|
||
recv_message.create_reply("ANNOUNCE OK").send
|
||
end
|
||
|
||
def send_request_capabilities
|
||
@conversation.thread('system').new_message("REQUEST EXISTS", { :node => node }).send
|
||
end
|
||
|
||
def send_request_call(conv_thread, node)
|
||
conv_thread.new_message("REQUEST CALL", { :node => node }).send
|
||
end
|
||
|
||
def send_request_exists(conv_thread, node)
|
||
conv_thread.new_message("REQUEST EXISTS", { :node => node }).send
|
||
end
|
||
|
||
def send_request_describe(conv_thread, node)
|
||
conv_thread.new_message("REQUEST DESCRIBE", { :node => node }).send
|
||
end
|
||
|
||
def send_error_protocol(error, fatal = false)
|
||
@conversation.thread('system').new_message("ERROR PROTOCOL", { :error => error }).send
|
||
@conversation.set_error_status(fatal)
|
||
end
|
||
|
||
def send_error_action(recv_message, error)
|
||
recv_message.create_reply("ERROR ACTION", { :error => error }).send
|
||
end
|
||
|
||
def send_reply_ack(recv_message)
|
||
recv_message.create_reply("REPLY ACK").send
|
||
end
|
||
|
||
def send_reply_decline(recv_message, reason)
|
||
recv_message.create_reply("REPLY DECLINE", { :reason => reason }).send
|
||
end
|
||
|
||
def send_reply_result(recv_message, result)
|
||
recv_message.create_reply("REPLY ACK", { :result => result }).send
|
||
end
|
||
|
||
def send_quit_decline(reason)
|
||
@conversation.thread('system').new_message("QUIT LEAVING", { :reason => reason }).send
|
||
end
|
||
|
||
def send_quit_leaving
|
||
@conversation.thread('system').new_message("QUIT LEAVING").send
|
||
end
|
||
end
|
||
|
||
class ConversationThread
|
||
attr_reader :conversation, :name, :id, :session
|
||
|
lib/cyborghood/cyborg/protocol.rb | ||
---|---|---|
#--
|
||
# CyborgHood, a distributed system management software.
|
||
# Copyright (c) 2009-2010 Marc Dequènes (Duck) <Duck@DuckCorp.org>
|
||
#
|
||
# This program is free software: you can redistribute it and/or modify
|
||
# it under the terms of the GNU General Public License as published by
|
||
# the Free Software Foundation, either version 3 of the License, or
|
||
# (at your option) any later version.
|
||
#
|
||
# This program is distributed in the hope that it will be useful,
|
||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
# GNU General Public License for more details.
|
||
#
|
||
# You should have received a copy of the GNU General Public License
|
||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||
#++
|
||
|
||
# Note: Event machine core is mono-thread, so this code is not threadsafe.
|
||
# Only long tasks will be run in threads, which do not care about all this.
|
||
|
||
|
||
module CyborgHood
|
||
module BotProtocol
|
||
VERSION = "0.1"
|
||
CAPABILITIES = []
|
||
|
||
@@request_callback = proc{|result| process_request_result(result) }
|
||
|
||
# TODO:
|
||
# - check for request/reply couples (reply to wrong of non-existent request)
|
||
|
||
def initialize(conversation)
|
||
@conversation = conversation
|
||
|
||
@negociation_received = false
|
||
@negociation_sent = false
|
||
@negociation_ok = false
|
||
end
|
||
|
||
def self.process_request_result(result)
|
||
if result[:error]
|
||
send_error_action(result[:reply_message], result[:error])
|
||
else
|
||
send_reply_result(result[:reply_message], result[:action_result])
|
||
end
|
||
end
|
||
|
||
def negociation_ok?
|
||
@negociation_ok
|
||
end
|
||
|
||
def process_received_message(message)
|
||
method = "receive_" + message.action_code.downcase.tr(" ", "_")
|
||
if respond_to? method
|
||
send(method, message)
|
||
else
|
||
send_error_protocol "unknown action"
|
||
end
|
||
end
|
||
|
||
def receive_announce_helo(message)
|
||
unless message.conv_thread.id == 0
|
||
return send_quit_decline "bad negociation"
|
||
end
|
||
unless message.parameters[:bot_name] =~ Conversation::BOT_ID_PATTERN
|
||
return send_quit_decline "bad bot name"
|
||
end
|
||
unless message.parameters[:protocol_version] == VERSION
|
||
return send_quit_decline "protocol version does not match"
|
||
end
|
||
@negociation_received = true
|
||
@conversation.set_peer_info(message.parameters[:bot_name], message.parameters[:capabilities])
|
||
|
||
if @negociation_sent
|
||
send_announce_ok(message)
|
||
@negociation_ok = true
|
||
else
|
||
send_announce_helo(message)
|
||
@negociation_sent = true
|
||
end
|
||
end
|
||
|
||
def receive_announce_ok(message)
|
||
unless @negociation_sent and @negociation_received
|
||
send_quit_decline "bad negociation"
|
||
end
|
||
@negociation_ok = true
|
||
end
|
||
|
||
def receive_request_capabilities(message)
|
||
send_reply_result(message, @conversation.capabilities + CAPABILITIES)
|
||
end
|
||
|
||
def receive_request_call(message)
|
||
unless @conversation.bot.interface.is_node? message.parameters[:node]
|
||
return send_error_action(message, "bad node")
|
||
end
|
||
send_reply_ack(message)
|
||
@conversation.bot.schedule_task(@@request_callback) do
|
||
result = {
|
||
:reply_message => message
|
||
}
|
||
begin
|
||
result[:action_result] = @conversation.bot.interface.call(message.conv_thread.session,
|
||
message.parameters[:node],
|
||
message.parameters[:data])
|
||
rescue
|
||
result[:error] = $!
|
||
end
|
||
end
|
||
end
|
||
|
||
def receive_request_exists(message)
|
||
unless @conversation.bot.interface.is_node? message.parameters[:node]
|
||
return send_error_action(message, "bad node")
|
||
end
|
||
send_reply_ack(message)
|
||
@conversation.bot.schedule_task(@@request_callback) do
|
||
{
|
||
:reply_message => message,
|
||
:action_result => @conversation.bot.interface.has_node? message.parameters[:node]
|
||
}
|
||
end
|
||
end
|
||
|
||
def receive_request_describe(message)
|
||
# TODO: implement when ready in the interface
|
||
send_quit_decline(message, "not implemented")
|
||
end
|
||
|
||
def send_announce_helo(recv_message = nil)
|
||
action_code = "ANNOUNCE HELO"
|
||
message = (recv_message.nil? ? @conversation.thread('system').new_message(action_code) :
|
||
recv_message.create_reply(action_code))
|
||
message.send
|
||
end
|
||
|
||
def send_announce_ok(recv_message)
|
||
recv_message.create_reply("ANNOUNCE OK").send
|
||
end
|
||
|
||
def send_request_capabilities
|
||
@conversation.thread('system').new_message("REQUEST EXISTS", { :node => node }).send
|
||
end
|
||
|
||
def send_request_call(conv_thread, node)
|
||
conv_thread.new_message("REQUEST CALL", { :node => node }).send
|
||
end
|
||
|
||
def send_request_exists(conv_thread, node)
|
||
conv_thread.new_message("REQUEST EXISTS", { :node => node }).send
|
||
end
|
||
|
||
def send_request_describe(conv_thread, node)
|
||
conv_thread.new_message("REQUEST DESCRIBE", { :node => node }).send
|
||
end
|
||
|
||
def send_error_protocol(error, fatal = false)
|
||
@conversation.thread('system').new_message("ERROR PROTOCOL", { :error => error }).send
|
||
@conversation.set_error_status(fatal)
|
||
end
|
||
|
||
def send_error_action(recv_message, error)
|
||
recv_message.create_reply("ERROR ACTION", { :error => error }).send
|
||
end
|
||
|
||
def send_reply_ack(recv_message)
|
||
recv_message.create_reply("REPLY ACK").send
|
||
end
|
||
|
||
def send_reply_decline(recv_message, reason)
|
||
recv_message.create_reply("REPLY DECLINE", { :reason => reason }).send
|
||
end
|
||
|
||
def send_reply_result(recv_message, result)
|
||
recv_message.create_reply("REPLY ACK", { :result => result }).send
|
||
end
|
||
|
||
def send_quit_decline(reason)
|
||
@conversation.thread('system').new_message("QUIT LEAVING", { :reason => reason }).send
|
||
end
|
||
|
||
def send_quit_leaving
|
||
@conversation.thread('system').new_message("QUIT LEAVING").send
|
||
end
|
||
end
|
||
end
|
Also available in: Unified diff
[evol] conversation/bot protocol rework §7 (refs #30)