Project

General

Profile

« Previous | Next » 

Revision cdd6154d

Added by Marc Dequènes almost 11 years ago

  • ID cdd6154d6bc7e5034408799b673c6766f806c293

[evol] conversation protocol complete rework + work on action protocol §1 (refs #30)

View differences:

lib/cyborghood/base/config.rb
28 28
    CONFFILE_GLOBAL = "cyborghood.conf"
29 29
    CONFFILE_BOT = "cyborghood_%s.conf"
30 30

  
31
    attr_accessor :bot_id
32

  
31 33
    def self.load(name = nil)
32 34
      # load all config parts
33 35
      g_config_raw = load_config_raw()
......
58 60
      end
59 61
    end
60 62

  
61
    protected
62

  
63
    def self.merge_into_default_config(conf_default, conf)
63
    protected    def self.merge_into_default_config(conf_default, conf)
64 64
      new_conf = conf_default.dup
65 65
      conf.each_pair do |k, v|
66 66
        if conf_default.has_key?(k) and conf_default[k].is_a?(Hash) and v.is_a?(Hash)
lib/cyborghood/cyborg.rb
28 28
      # load config
29 29
      Config.load(self.human_name)
30 30
      @config = Config.instance
31
      @config.bot_id = self.class.name
31 32

  
32 33
      # setup logs
33 34
      unless @config.log.nil?
lib/cyborghood/cyborg/conversation.rb
16 16
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 17
#++
18 18

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

  
21 22

  
22 23
module CyborgHood
24

  
25
  class Protocol
26
    VERSION = "0.1"
27

  
28
    def initialize(conversation)
29
      @conversation = conversation
30
    end
31

  
32
    def receive_announce_helo(parameters)
33
      unless parameters[:bot_name] =~ Conversation::BOT_ID_PATTERN
34
        return send_error_protocol "bad bot name"
35
      end
36
      unless parameters[:protocol_version] == VERSION
37
        @conversation.set_fatal
38
        return send_error_protocol "protocol version does not match"
39
      end
40
      @conversation.set_peer_info(parameters[:bot_name], parameters[:capabilities])
41
      send_reply_ok
42
    end
43

  
44
    def send_error_protocol(msg = nil)
45
      @conversation.set_reply("ERROR PROTO", { :error => msg || "" })
46
    end
47

  
48
    def send_reply_ack
49
      @conversation.set_reply("REPLY ACK")
50
    end
51
  end
52

  
23 53
  class Conversation < EventMachine::Protocols::LineAndTextProtocol
24 54
    private_class_method :new
25 55

  
......
27 57
    MaxLineLength = 16*1024
28 58

  
29 59
    EOD = "\033[0J"
30
    COMMAND_PATTERN = "^#{CyborgServerInterfaceBase::NODE_PATTERN}(?: ([+?]+))?$"
60
    BOT_ID_PATTERN = "[a-zA-Z0-9]+"
61
    ACTION_PATTERN = "^(#{BOT_ID_PATTERN})(\d{4})-(\d{4})([+]?) ([a-zA-Z0-9 ]+)$"
31 62
    MAXIMUM_ERROR_COUNT = 3
63
    MAXIMUM_LINES = 1024
64

  
65
    attr_reader :interface
32 66

  
33 67
    def initialize(interface)
34 68
      @interface = interface
......
37 71

  
38 72
      @config = Config.instance
39 73
      @split_data_mode = false
40
      @split_data_cmd = nil
74
      @split_data_action = nil
41 75
      @split_data = []
42
      @session = Session.new
76
      # one session for each conversation thread
77
      @sessions = {}
43 78
      @error_count = 0
79
      @fatal_error = false
80
      @actions_wip = {}
81
      @peer_id = nil
82
      @peer_capabilities = []
83

  
84
      @protocol = Protocol.new(this)
44 85
    end
45 86

  
46 87
    def send_line(msg)
......
50 91

  
51 92
    def post_init
52 93
      logger.debug "New conversation with #{identifier}"
53
      send_line "220 #{PRODUCT} #{VERSION} - #{@config.bot_name}"
54 94
    end
55 95

  
56 96
    def receive_line(data)
57

  
58 97
      return if data.empty?
59 98

  
99
      @reply_action = "Internal error"
100
      @reply_parameters = {}
101

  
60 102
      if data == EOD
61 103
        logger.debug "Received EOD [#{identifier}]"
62 104
        exit_split_mode
......
64 106
        if @split_data_mode
65 107
          logger.debug "Received data (split mode) [#{identifier}]: #{data}"
66 108

  
67
          @split_data << data
109
          if @split_data.size > MAXIMUM_LINES
110
            reply_fatal_error "overflow"
111
          else
112
            @split_data << data
113
          end
68 114
        else
69 115
          logger.debug "Received data [#{identifier}]: #{data}"
70 116

  
71
          unless data =~ Regexp.new(COMMAND_PATTERN)
117
          if data =~ Regexp.new(ACTION_PATTERN)
118
            @thread_id = $1
119
            @action_id = $2
120
            flags = $3 || ""
121
            action = $4
122

  
123
            if @actions_wip[@thread_id].nil? or not @actions_wip[@thread_id].include?(@action_id)
124
              @sessions[@thread_id] ||= Session.new
125
              if flags.index '+'
126
                enter_split_mode(action)
127
              else
128
                receive_action(action)
129
              end
130
            else
131
              reply_fatal_error "wip action id reused"
132
            end
133
          else
72 134
            logger.error "Error [#{identifier}]: syntax error"
73 135
            @error_count += 1
74 136

  
75 137
            if @error_count >= MAXIMUM_ERROR_COUNT
76
              send_line "503 too much errors, terminating"
77
              close_connection_after_writing
138
              reply_fatal_error "too much errors, terminating"
78 139
            else
79
              send_line "552 syntax error in command"
140
              reply_syntax_error "bad action"
80 141
            end
81
            return
82
          end
83
          cmd = $1
84
          flags = $2 || ""
85
          @error_count = 0
86

  
87
          if flags.index '?'
88
            send_line "250+ ok"
89
            send_line({'exists' => @interface.has_node?(cmd)}.to_yaml)
90
            return
91
          end
92

  
93
          if flags.index '+'
94
            enter_split_mode(cmd)
95
          else
96
            receive_command(cmd)
97 142
          end
98 143
        end
99 144
      end
145

  
146
      send_reply
147
      close_connection_after_writing if @fatal_error
100 148
    end
101 149

  
102
    def receive_command(cmd, data = nil)
103
      logger.debug "Executing command '#{cmd}' [#{identifier}]"
104
      send_line @interface.call(@session, cmd, data)
150
    def receive_action(action, data = nil)
151
      logger.debug "Executing action '#{action}' [#{identifier}]"
152

  
153
      unless data.nil?
154
        parameters = YAML.parse(data)
155
        reply_syntax_error "bad parameters"
156
        return
157
      end
158

  
159
      # use a Protocol object and convert back result to the conversation level protocol
160
      # we need to use EM.defer or EM.spawn+Deferrable, preferably after parsing, to avoid spawning threads for nothing
161
      #send_line @interface.call(@session, action, data)
105 162
    end
106 163

  
107 164
    def receive_error(msg)
......
110 167

  
111 168
    def unbind
112 169
      logger.debug "Conversation finished with #{identifier}"
113
      @session.clear
170
      @sessions.each_values {|s| s.clear }
171
    end
172

  
173
    def set_peer_info(id, capabilities)
174
      @peer_id = id
175
      @peer_capabilities = capabilities || []
176
    end
177

  
178
    def set_reply(action, parameters = nil)
179
      @reply_action =  action
180
      @reply_parameters = parameters
181
    end
182

  
183
    def set_fatal
184
      @fatal_error = true
185
    end
186

  
187
    def current_session
188
      @sessions[@thread_id]
114 189
    end
115 190

  
116 191
    protected
117 192

  
118
    def enter_split_mode(cmd)
193
    def reply_syntax_error(msg = nil)
194
      msg = "syntax error" + (msg ? ": " + msg : "")
195
      @protocol.send_error_protocol(msg)
196
    end
197

  
198
    def reply_fatal_error(msg = nil)
199
      msg = "fatal error" + (msg ? ": " + msg : "")
200
      @protocol.send_error_protocol(msg)
201
      set_fatal
202
    end
203

  
204
    def send_reply
205
      send_line "#{@config.bot_id}-#{@thread_id}-#{@action_id}" + (@reply_parameters.nil? : "" : "+") + " #{@reply_action}"
206
      unless @reply_parameters.nil?
207
        @reply_parameters.to_yaml.split.each {|l| send_line l }
208
        send_line EOD
209
      end
210
    end
211

  
212
    def enter_split_mode(action)
119 213
      if @split_data_mode
120 214
        logger.error "Error [#{identifier}]: already in split mode"
121 215
        send_line "551 protocol error"
122 216
        @split_data_mode = false
123
        @split_data_cmd = nil
217
        @split_data_action = nil
124 218
      else
125
        logger.debug "Entered split mode for command '#{cmd}' [#{identifier}]"
219
        logger.debug "Entered split mode for action '#{action}' [#{identifier}]"
126 220
        @split_data_mode = true
127
        @split_data_cmd = cmd
221
        @split_data_action = action
128 222
      end
129 223
      @split_data = []
130 224
    end
131 225

  
132 226
    def exit_split_mode
133 227
      if @split_data_mode
134
        logger.debug "Quit split mode for command '#{@split_data_cmd}' [#{identifier}]"
135
        receive_command(@split_data_cmd, @split_data.join("\n"))
228
        logger.debug "Quit split mode for action '#{@split_data_action}' [#{identifier}]"
229
        receive_action(@split_data_action, @split_data.join("\n"))
136 230
      else
137 231
        logger.error "Error [#{identifier}]: not in split mode"
138 232
        send_line "551 protocol error"
139 233
      end
140 234
      @split_data_mode = false
141
      @split_data_cmd = nil
235
      @split_data_action = nil
142 236
      @split_data = []
143 237
    end
144 238
  end

Also available in: Unified diff