class MTik::Request

A MikroTik API request object is stored as an array of MikroTik API-style words, the first word being the command, subsequent words (if any) are command arguments. Each request will automatically have a unique tag generated (so any “.tag=value” arguments will be ignored). A request is incomplete until the final “!done” response sentence has been received.

Attributes

await_completion[R]
command[R]
reply[R]
state[R]
tag[R]

Public Class Methods

bytepack(num, size) click to toggle source

Utility method for packing an unsigned integer as a binary byte string of variable length

# File lib/mtik/request.rb, line 162
def self.bytepack(num, size)
  s = String.new
  if RUBY_VERSION >= '1.9.0'
    s.force_encoding(Encoding::BINARY)
  end
  x = num < 0 ? -num : num  ## Treat as unsigned
  while size > 0
    size -= 1
    s = (x & 0xff).chr + s
    x >>= 8
  end
  raise RuntimeError.new(
    "Number #{num} is too large to fit in #{size} bytes."
  ) if x > 0
  return s
end
new(await_completion, command, *args, &callback) click to toggle source

Create a new MTik::Request.

await_completion

A boolean parameter indicating when callback(s) should be called. A value of true will result in callback(s) only being called once, when the final +!done+ response is received. A value of false means callback(s) will be called each time a response sentence is received.

command

The MikroTik API command to execute (a String). Examples:

"/interface/getall"
"/ip/route/add"
args

Zero or more String arguments for the command, already encoded in “=key=value”, “.id=value”, or “?query” API format.

callback

A Proc or code block may be passed which will be called with two arguments:

  1. this MTik::Request object; and

  2. the most recently received response sentence.

When or how often callbacks are called depends on whether the await_completion parameter is true or false (see above).

Calls superclass method
# File lib/mtik/request.rb, line 67
def initialize(await_completion, command, *args, &callback)
  @reply            = MTik::Reply.new
  @command          = command
  @await_completion = await_completion
  @state            = :new  ## :new, :sent, :canceled, :complete
  @conn             = nil

  args.flatten!
  @callbacks = Array.new
  if block_given?
    @callbacks.push(callback)
  elsif args.length > 0 && args[args.length-1].is_a?(Proc)
    @callbacks.push(args.pop)
  end

  super(&nil)
  ## Add command to the request sentence list:
  self.push(command)

  ## Add all arguments to the request sentence list:
  self.addargs(*args)

  ## Append a unique tag for the request:
  @tag = @@tagspace.to_s
  @@tagspace += 1
  self.push(".tag=#{@tag}")
end
to_tikword(str) click to toggle source

Another utility method to encode a byte string as a valid API “word”

# File lib/mtik/request.rb, line 181
def self.to_tikword(str)
  str = str.dup
  if RUBY_VERSION >= '1.9.0'
    str.force_encoding(Encoding::BINARY)
  end
  if str.length < 0x80
    return str.length.chr + str
  elsif str.length < 0x4000
    return bytepack(str.length | 0x8000, 2) + str
  elsif str.length < 0x200000
    return bytepack(str.length | 0xc00000, 3) + str
  elsif str.length < 0x10000000
    return bytepack(str.length | 0xe0000000, 4) + str
  elsif str.length < 0x0100000000
    return 0xf0.chr + bytepack(str.length, 5) + str
  else
    raise RuntimeError.new(
      "String is too long to be encoded for " +
      "the MikroTik API using a 4-byte length!"
    )
  end
end

Public Instance Methods

addarg(arg) click to toggle source

Append a single argument to the not-yet-sent request

# File lib/mtik/request.rb, line 129
def addarg(arg)
  unless /^\.tag=/.match(arg)
    self.push(arg)
  end
end
addargs(*args) click to toggle source

Append one or more arguments to the not-yet-sent request

# File lib/mtik/request.rb, line 110
def addargs(*args)
  ## Add all additional arguments to the request sentence list:
  args.each do |arg|
    if arg.is_a?(Hash)
      ## Prepend argument keys that don't begin with the API-
      ## command-specific parameter character '.', nor the
      ## normal parameter charactre '=', nor the query character
      ## '?' with the ordinary parameter character '=':
      arg.each do |key, value|
        key = '=' + key unless /^[\?\=\.]/.match(key)
        addarg(key + '=' + value)
      end
    else
      addarg(arg)
    end
  end
end
append_callback(*callbacks, &callback) click to toggle source

Add one or more callback Procs and/or a code block to the callback(s) that will be executed upon reply (either complete or partial, depending on the await_completion setting)

# File lib/mtik/request.rb, line 99
def append_callback(*callbacks, &callback)
  callbacks.flatten!
  callbacks.each do |cb|
    @callbacks.push(cb)
  end
  if block_given?
    @callbacks.push(callback)
  end
end
callback(sentence) click to toggle source

Execute all callbacks, passing sentence along as the second parameter to each callback.

# File lib/mtik/request.rb, line 145
def callback(sentence)
  case @callbacks.length
  when 0
    return nil
  when 1
    return @callbacks[0].call(self, sentence)
  else
    result = Array.new
    @callbacks.each do |cb|
      result.push(cb.call(self, sentence))
    end
    return result
  end
end
cancel(&callback) click to toggle source

Cancel a 'sent' request:

# File lib/mtik/request.rb, line 241
def cancel(&callback)
  if @state != :sent
    raise MTik::Error.new(
      "Method MTik::Request#cancel() called with state '#{@state}' " +
      "(should only call when state is :sent)"
    )
  end
  @conn.send_request(true, '/cancel', '=tag=' + @tag, &callback)
  @state = :canceled
end
cancel_each(&callback) click to toggle source

Cancel a 'sent' request:

# File lib/mtik/request.rb, line 253
def cancel_each(&callback)
  if @state != :sent
    raise MTik::Error.new(
      "Method MTik::Request#cancel() called with state '#{@state}' " +
      "(should only call when state is :sent)"
    )
  end
  @conn.send_request(false, '/cancel', '=tag=' + @tag, &callback)
  @state = :canceled
end
conn(c) click to toggle source

Associate this request with a connection:

# File lib/mtik/request.rb, line 205
def conn(c)
  unless c.is_a?(MTik::Connection)
    raise RuntimeError.new(
      "Unexpected class '#{c.class}' in MTik::Request#conn() " +
      "(expected MTik::Connection)"
    )
  end
  unless @conn.nil?
    raise MTik::Error.new(
      "Method MTik::Request#conn() called when MTik::Request " +
      "is already associated with an MTik::Connection object."
    )
  end
  @conn = c
end
done!() click to toggle source

Method the internal parser calls to flag this reply as completed upon receipt of a “!done” reply sentence. WARNING: If you call this manually and another sentence arrives, an exception will be raised!

# File lib/mtik/request.rb, line 268
def done!
  @state = :complete
  return true
end
done?() click to toggle source

Return the boolean completion status of the request, true if complete, false if not-yet-complete.

# File lib/mtik/request.rb, line 137
def done?
  return @state == :complete
end
request() click to toggle source

Encode this request as a binary byte string ready for transmission to a MikroTik device

# File lib/mtik/request.rb, line 223
def request
  ## Encode the request for sending to the device:
  return self.map {|w| MTik::Request::to_tikword(w)}.join + 0x00.chr
end
send() click to toggle source

Send the request over the associated connection:

# File lib/mtik/request.rb, line 229
def send
  if @conn.nil?
    raise MTik::Error.new(
      "Method MTik::Request#send() called when MTik::Request " +
      "is not yet associated with an MTik::Connection object."
    )
  end
  @state = :sent
  return @conn.xmit(self)
end