mikrotik 0.0.1

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.
@@ -0,0 +1,9 @@
1
+ .bundle
2
+ .idea
3
+ .redcar
4
+ .yardoc
5
+ Gemfile.lock
6
+ pkg
7
+ doc
8
+ !doc/setup.rb
9
+ *.gem
@@ -0,0 +1 @@
1
+ --no-private -e ./doc/setup.rb
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source :rubygems
2
+
3
+ gem 'eventmachine'
4
+
5
+ group :development do
6
+ gem 'rspec'
7
+ gem 'yard'
8
+ gem 'jeweler'
9
+ gem 'guard'
10
+ gem 'guard-yard'
11
+ end
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'yard' do
5
+ watch(%r{app/.+\.rb})
6
+ watch(%r{lib/.+\.rb})
7
+ watch(%r{ext/.+\.c})
8
+ end
@@ -0,0 +1,4 @@
1
+ Mikrotik API Client
2
+ ===================
3
+
4
+ * **API Documentation**: [View Online](http://ominiom.github.com/mikrotik/)
@@ -0,0 +1,47 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ require 'rubygems'
13
+ require 'rake'
14
+
15
+ begin
16
+ require 'jeweler'
17
+ Jeweler::Tasks.new do |gem|
18
+ gem.name = "mikrotik"
19
+ gem.summary = %Q{Mikrotik API protocol client in Ruby}
20
+ gem.description = %Q{An implementation of the Mikrotik API protocol in Ruby/EventMachine}
21
+ gem.email = "iain@ominiom.com"
22
+ gem.homepage = "http://github.com/ominiom/mikrotik"
23
+ gem.authors = ["Iain Wilson"]
24
+ gem.files = FileList["lib/*.rb", "lib/*/*.rb", "lib/*/*/*.rb"]
25
+ end
26
+ Jeweler::GemcutterTasks.new
27
+ rescue LoadError
28
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
29
+ end
30
+
31
+ require 'rake/testtask'
32
+ Rake::TestTask.new(:test) do |test|
33
+ test.libs << 'lib' << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+
38
+ task :default => :test
39
+
40
+ begin
41
+ require 'yard'
42
+ YARD::Rake::YardocTask.new
43
+ rescue LoadError
44
+ task :yardoc do
45
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
46
+ end
47
+ end
@@ -0,0 +1,34 @@
1
+ # Add lib to load path
2
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'mikrotik'
5
+
6
+ EventMachine.run do
7
+ client = Mikrotik.connect :host => "192.168.1.254", :port => 8728, :username => "admin", :password => "debUwu8A"
8
+
9
+ puts "Connecting..."
10
+
11
+ client.on_login_success do |client|
12
+ puts "Login successful"
13
+
14
+ # Run a command every 5 seconds
15
+ EM.add_timer(EM::PeriodicTimer.new(5) do
16
+ client.execute(client.command(:interface, :print).returning(:name, :bytes).where(:name => :ether1).on_reply do |reply|
17
+ # Sum up the in and out counts
18
+ bytes = reply.result(:bytes).split('/').map(&:to_i).inject(0) { |i, n| i = i + n }
19
+ # Format nicely
20
+ total = sprintf "%.2f", (bytes / 1024.0 / 1024.0)
21
+ # Output
22
+ puts "Interface '#{reply.result :name}' has transferred #{total} MB"
23
+ end)
24
+ end)
25
+
26
+ end
27
+
28
+ trap 'SIGINT' do
29
+ client.disconnect
30
+ puts "Disconnected from #{client.options[:host]}"
31
+ exit
32
+ end
33
+
34
+ end
@@ -0,0 +1,8 @@
1
+ client.execute(client.command(:ip, :dhcp_server, :lease, :getall).on_done do |replies|
2
+ puts replies.collect { |reply| "IP '#{reply.result :address}' assigned to MAC #{reply.result('mac-address')}" }
3
+ end)
4
+
5
+ client.on_pending_complete do |client|
6
+ puts "All commands completed, closing session..."
7
+ client.disconnect
8
+ end
@@ -0,0 +1,8 @@
1
+ client.execute(client.command(:system, :resource, :print).returning(:uptime).on_reply do |reply|
2
+ puts "System uptime is #{reply.result :uptime}"
3
+ end)
4
+
5
+ client.on_pending_complete do |client|
6
+ puts "All commands completed, closing session..."
7
+ client.disconnect
8
+ end
@@ -0,0 +1,41 @@
1
+ # @private
2
+ module Events
3
+
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ # @return [Boolean] If the event has a handler registered
9
+ def has_event_handler?(event)
10
+ @events ||= {}
11
+ !@events["on_#{event}".to_sym].nil?
12
+ end
13
+
14
+ module ClassMethods
15
+
16
+ # @param [Array<String, Symbol>] events Array of event names
17
+ # @example
18
+ # has_events :delete, :create
19
+ def has_events(*events)
20
+ events.map { |event| event.to_s }.each do |event|
21
+ self.class_eval <<-"END"
22
+ def on_#{event}(*args, &block)
23
+ @events ||= {}
24
+ if block_given? then
25
+ @events[:on_#{event}] = block.to_proc
26
+ else
27
+ @events[:on_#{event}].call(*args) unless @events[:on_#{event}].nil?
28
+ end
29
+ return self
30
+ end
31
+ END
32
+ end
33
+ end
34
+
35
+ def has_event(event)
36
+ has_events(*[event])
37
+ end
38
+
39
+ end
40
+
41
+ end
@@ -0,0 +1,27 @@
1
+ # @private
2
+ class String
3
+
4
+ def to_mikrotik_word
5
+ str = self.dup
6
+ if RUBY_VERSION >= '1.9.0'
7
+ str.force_encoding(Encoding::BINARY)
8
+ end
9
+ if str.length < 0x80
10
+ return str.length.chr + str
11
+ elsif str.length < 0x4000
12
+ return Microtik::Utilities.bytepack(str.length | 0x8000, 2) + str
13
+ elsif str.length < 0x200000
14
+ return Microtik::Utilities.bytepack(str.length | 0xc00000, 3) + str
15
+ elsif str.length < 0x10000000
16
+ return Microtik::Utilities.bytepack(str.length | 0xe0000000, 4) + str
17
+ elsif str.length < 0x0100000000
18
+ return 0xf0.chr + Microtik::Utilities.bytepack(str.length, 5) + str
19
+ else
20
+ raise RuntimeError.new(
21
+ "String is too long to be encoded for " +
22
+ "the MikroTik API using a 4-byte length!"
23
+ )
24
+ end
25
+ end
26
+
27
+ end
@@ -0,0 +1,42 @@
1
+ require 'eventmachine'
2
+ require 'digest/md5'
3
+
4
+ # TODO : Allow underscores to be used instead of dashes in command paths and properties - e.g. in dhcp-server and mac-address
5
+
6
+ module Mikrotik
7
+
8
+ module Protocol; end;
9
+
10
+ # @return [Boolean] Whether debugging prints are enabled or not
11
+ def self.debugging
12
+ false
13
+ end
14
+
15
+ # Prints message if debugging is enabled
16
+ # @param [Array<String>] message Parts of the message
17
+ def self.debug(message)
18
+ puts message.join(' ') if debugging
19
+ end
20
+
21
+ # Shorthand for Mikrotik::Client.connect
22
+ # @return [Mikrotik::Client]
23
+ def self.connect(*args)
24
+ Client.connect(*args)
25
+ end
26
+
27
+ end
28
+
29
+ # Add ourselves to the load path
30
+ $: << File.dirname(__FILE__)
31
+
32
+ # Load up the code
33
+ require 'extensions/string'
34
+ require 'extensions/events'
35
+ require 'mikrotik/version'
36
+ require 'mikrotik/errors'
37
+ require 'mikrotik/client'
38
+ require 'mikrotik/connection'
39
+ require 'mikrotik/command'
40
+ require 'mikrotik/utilities'
41
+ require 'mikrotik/protocol/parser'
42
+ require 'mikrotik/protocol/sentence'
@@ -0,0 +1,124 @@
1
+ # Contains all methods for interacting with a device
2
+ # such as logging in and executing commands
3
+ #
4
+ # @see Mikrotik::Client.connect
5
+ module Mikrotik::Client
6
+ include Events
7
+
8
+ # Fired when the API login command has returned successfully.
9
+ # After this event has fired it is safe to start sending commands
10
+ has_event :login_success
11
+
12
+ # Fired when the API login command returns an error -
13
+ # for example in cases where the credentials were wrong
14
+ has_event :login_failure
15
+
16
+ # Fired when the queue of commands awaiting completion
17
+ # becomes empty
18
+ has_event :pending_complete
19
+
20
+ # @return [Hash] Options used for the connection
21
+ # @see Client#connect
22
+ attr_reader :options
23
+
24
+ # Connects to a RouterOS device. Required options are +:host+
25
+ # and +:password+ - +:username+ and +:port+ default
26
+ # to +admin+ and +8728+ respectively
27
+ #
28
+ # @param [Hash] options +:test+ Hash of connection options
29
+ # @option options [String] :host Device's IP address
30
+ # @option options [Integer] :port (8728) Port of the API service on the device
31
+ # @option options [String] :username ('admin') API username to authenticate with
32
+ # @option options [String] :password Password for the given username
33
+ # @return [Client]
34
+ # @raise [ArgumentError] If not all required options are given
35
+ # @example Connection with minimum options set
36
+ # Mikrotik::Client.connect({
37
+ # :host => '192.168.1.1',
38
+ # :password => 'topsecret'
39
+ # })
40
+ # @example Connection with all options set to override defaults
41
+ # Mikrotik::Client.connect({
42
+ # :host => '10.200.0.21',
43
+ # :port => 3450,
44
+ # :username => 'apiuser',
45
+ # :password => 'topsecret'
46
+ # })
47
+ #
48
+ def self.connect(options = { :username => 'admin', :port => 8728 })
49
+ defaults = { :username => 'admin', :port => 8728 }
50
+ options = defaults.merge(options)
51
+ # Check required options have been given
52
+ all_required_options = !([:host, :port, :username, :password].map { |option|
53
+ options.key?(option) && !options[option].nil?
54
+ }.include?(false))
55
+ raise ArgumentError, "Options for host and password must be given" unless all_required_options
56
+
57
+ c = EM.connect options[:host], options[:port], self, options
58
+ c.pending_connect_timeout = 10.0
59
+ c
60
+ end
61
+
62
+ # Logs in to the device. This is called automatically when
63
+ # the TCP connection succeeds. To be sure the device is ready
64
+ # to +execute+ commands before sending any register an event handler
65
+ # on +on_login_success+ and send commands from there
66
+ def login
67
+ execute command(:login).on_reply { |reply|
68
+
69
+ challenge = Mikrotik::Utilities.hex_to_bin(reply.result :ret)
70
+ response = "00" + Digest::MD5.hexdigest(0.chr + @options[:password] + challenge)
71
+
72
+ execute command(:login).with(
73
+ :name => @options[:username],
74
+ :response => response
75
+ ).on_done { |replies|
76
+ Mikrotik.debug [:client, :on_login_success]
77
+ @logged_in = true
78
+ on_login_success(self)
79
+ }.on_trap { |trap|
80
+ Mikrotik.debug [:client, :on_login_failure]
81
+ if has_event_handler? :on_login_failure then
82
+ on_login_failure(self)
83
+ else
84
+ raise Mikrotik::Errors::UnhandledTrap, 'Login failed'
85
+ end
86
+ }
87
+
88
+ }
89
+ end
90
+
91
+ # Sends a command to the device
92
+ # @param command [Mikrotik::Command] The command to be executed
93
+ # @return [Mikrotik::Command] The command executed
94
+ def execute(command)
95
+ send command
96
+ end
97
+
98
+ # @return [Boolean] Status of the session login
99
+ def logged_in?
100
+ @logged_in
101
+ end
102
+
103
+ # Shortcut for creating a Mikrotik::Command
104
+ # @return Mikrotik::Command
105
+ # @see Mikrotik::Command
106
+ def command(*path)
107
+ Mikrotik::Command.new(*path)
108
+ end
109
+
110
+ def inspect
111
+ "#<#{self.class.name}:0x#{self.object_id} host=#{@options[:host]}>"
112
+ end
113
+
114
+ def initialize(opts = {})
115
+ @closing = false
116
+ @logged_in = false
117
+ @options = opts
118
+ @events = {}
119
+ @buffer = ""
120
+ @next_tag = 1
121
+ extend Mikrotik::Connection
122
+ end
123
+
124
+ end
@@ -0,0 +1,81 @@
1
+ # Class encapsulating a API command to be executed on the device
2
+ class Mikrotik::Command
3
+ include Events
4
+
5
+ # Fired when the command completes
6
+ # @yield [replies]
7
+ # @yieldparam [false, Array<Mikrotik::Protocol::Sentence>] replies All replies received from the device for this command
8
+ has_events :done
9
+
10
+ # Fired on each reply received from the command
11
+ # @yield [reply]
12
+ # @yieldparam [false, Mikrotik::Protocol::Sentence] reply The sentence received in reply
13
+ has_event :reply
14
+
15
+ # Fired when an error response is received from the command
16
+ # @yield [error_message]
17
+ # @yieldparam [false, String] error_message Error description
18
+ has_event :trap
19
+
20
+ # @return [Array<Mikrotik::Protocol::Sentence>] All sentences sent in response to this command so far
21
+ attr_reader :replies
22
+
23
+ # @return [Hash] All option name/value pairs set on the command
24
+ attr_reader :options
25
+
26
+ # @return [String] Path to the command
27
+ attr_reader :command
28
+
29
+ # Creates a new command object
30
+ # @param [Array<String, Symbol>] path Path of the API command
31
+ def initialize(*command_path)
32
+ @command = command_path.collect { |part| "#{part}".gsub('_', '-') }.join('/')
33
+ @command = "/#{@command}" unless @command.start_with?('/')
34
+ @options = {}
35
+ @replies = []
36
+ end
37
+
38
+ # Adds property names and values to the command
39
+ # @param [Hash] options
40
+ def with(options = {})
41
+ options.each_pair do |option, value|
42
+ @options["=#{option}"] = value
43
+ end
44
+ self
45
+ end
46
+
47
+ # Adds querying conditions to the command
48
+ # @param [Hash] conditions
49
+ def where(conditions = {})
50
+ conditions.each_pair do |property, value|
51
+ @options["?#{property}"] = value
52
+ end
53
+ self
54
+ end
55
+
56
+ # Specifies what properties should be returned by the device in response to this command
57
+ #
58
+ # @param [Array<String, Symbol>] properties List of the names of properties to request in response
59
+ # @return [self]
60
+ def returning(*properties)
61
+ @options['=.proplist'] ||= []
62
+ properties.each do |property|
63
+ @options['=.proplist'] << "#{property}"
64
+ end
65
+ self
66
+ end
67
+
68
+ # Encodes the command for transmission
69
+ # @return [String] Command encoded as an API sentence in binary string format
70
+ def encoded
71
+ @command.to_mikrotik_word + @options.collect { |key, value|
72
+ case value.class.name
73
+ when 'Array'
74
+ [key, value.collect { |item| "#{item}" }.join(',')].join '='
75
+ else
76
+ "#{key}=#{value}"
77
+ end
78
+ }.map { |option| option.to_mikrotik_word }.join + 0x00.chr
79
+ end
80
+
81
+ end
@@ -0,0 +1,84 @@
1
+ # Contains logic for sending and receiving data to and from the device
2
+ module Mikrotik::Connection
3
+
4
+ # @private
5
+ def connection_completed
6
+ @commands = {}
7
+ @sentences = []
8
+ @parser = Mikrotik::Protocol::Parser.new
9
+ @parser.on_sentence do |sentence|
10
+ handle_reply(sentence)
11
+ end
12
+ login
13
+ end
14
+
15
+ # Disconnects from the device
16
+ def disconnect
17
+ Mikrotik.debug [:connection, :disconnect]
18
+ @closing = true
19
+ close_connection(true)
20
+ end
21
+
22
+ # @private
23
+ def send(command)
24
+ command.options['.tag'] = @next_tag
25
+ Mikrotik.debug [:connection, :send, command.encoded]
26
+ @commands[@next_tag] = command
27
+ send_data(command.encoded)
28
+ @next_tag = @next_tag + 1
29
+ command
30
+ end
31
+
32
+ # @private
33
+ def receive_data(data)
34
+ Mikrotik.debug [:connection, :receive, data.size, data]
35
+ @parser << data
36
+ end
37
+
38
+ # @private
39
+ def unbind
40
+ raise Mikrotik::Errors::ConnectionDropped unless @closing
41
+ end
42
+
43
+ private
44
+
45
+ # @private
46
+ def handle_reply(reply)
47
+ unless reply.tagged? then
48
+ Mikrotik.debug [:connection, :handle_reply, :untagged]
49
+ return
50
+ end
51
+ Mikrotik.debug [:connection, :handle_reply, :tagged, reply.tag]
52
+ # Retreive the command this reply is intended for by its tag
53
+ command = @commands[reply.tag]
54
+ command.replies << reply unless reply.completely_done?
55
+ # Catch those nasty errors
56
+ if reply.trap? then
57
+ Mikrotik.debug [:command, :on_trap]
58
+ # If there's an error handler, fire it...
59
+ if command.has_event_handler?(:on_trap) then
60
+ command.on_trap(reply.param(:message))
61
+ else
62
+ # ...if not, raise an error
63
+ raise Mikrotik::Errors::UnhandledTrap, reply.result(:message) || "Unknown error"
64
+ end
65
+ return
66
+ end
67
+ # Still fire on_reply events for done messages that contain more data
68
+ unless reply.completely_done? then
69
+ Mikrotik.debug [:command, :on_reply]
70
+ command.on_reply(reply)
71
+ end
72
+ # If this was the last reply, fire the on_done event handler
73
+ if reply.done? then
74
+ Mikrotik.debug [:command, :on_done, reply.tag]
75
+ command.on_done(command.replies)
76
+ # As the command has completed remove it from the list of active commands
77
+ @commands.delete(reply.tag)
78
+ Mikrotik.debug [:client, :commands, :size, @commands.size]
79
+ end
80
+ # If there are no more active commands fire the event
81
+ on_pending_complete(self) if @commands.empty?
82
+ end
83
+
84
+ end
@@ -0,0 +1,11 @@
1
+ module Mikrotik::Errors
2
+
3
+ # Raised when an error response is received to a
4
+ # command with no on_trap event handler
5
+ class UnhandledTrap < RuntimeError; end;
6
+
7
+ # Raised when the connection to the device
8
+ # disconnects unexpectedly
9
+ class ConnectionDropped < RuntimeError; end;
10
+
11
+ end
@@ -0,0 +1,126 @@
1
+ # Responsible for extracting replies from API sentence data
2
+ class Mikrotik::Protocol::Parser
3
+ include Events
4
+
5
+ # Fired when the parser extracts a complete API sentence
6
+ # @yield [Mikrotik::Protocol::Sentence] sentence
7
+ has_event :sentence
8
+
9
+ def initialize
10
+ @data = ''
11
+ @data.force_encoding(Encoding::BINARY) if RUBY_VERSION > '1.9'
12
+ reset!
13
+ end
14
+
15
+ # Discards the sentence currently being parsed
16
+ def reset!
17
+ @sentence = Mikrotik::Protocol::Sentence.new
18
+ end
19
+
20
+ # Adds data to the parser buffer and runs the parser
21
+ def <<(data)
22
+ @data << data
23
+ success = true
24
+ success = get_sentence while success
25
+ end
26
+
27
+ PAIR = /^(.+)=(.+)$/
28
+
29
+ # Indentifies the size of the length field in bits
30
+ # @return boolean, integer
31
+ def get_length_size
32
+ return false if @data.empty?
33
+
34
+ # High bits used for length type
35
+ # Low bits used as first bits of length
36
+
37
+ # 0xxxxxxx = 7 bit length
38
+ # 10xxxxxx = 14 bit length
39
+ # 110xxxxx = 21 bit length
40
+ # 1110xxxx = 28 bit length
41
+ # 11110000 = 32 bit length follows
42
+
43
+ t = @data.unpack('C').first
44
+
45
+ return 7 if t & 0b10000000 == 0
46
+ return 14 if t & 0b01000000 == 0
47
+ return 21 if t & 0b00100000 == 0
48
+ return 28 if t & 0b00010000 == 0
49
+ return 32 if t == 0b11110000
50
+
51
+ raise ArgumentError, "Invalid length type encoding"
52
+ end
53
+
54
+ def get_length
55
+ size = get_length_size
56
+
57
+ return false unless size
58
+
59
+ need = bytes(size + 1)
60
+ return false unless @data.size >= need
61
+
62
+ length = nil
63
+ field = @data[0, need].unpack('C*')
64
+
65
+ case size
66
+ when 7
67
+ length = field[0] & 0x7f
68
+ when 14
69
+ length = ((field[0] & 0x3f) << 8) | field[1]
70
+ when 21
71
+ length = ((field[0] & 0x1f) << 16) | field[1] << 8 | field[2]
72
+ when 28
73
+ length = ((field[0] & 0x0f) << 24) | field[1] << 16 | field[2] << 8 | field[3]
74
+ when 32
75
+ length = field[1..4].pack('C4').unpack('N').first
76
+ end
77
+
78
+ if length
79
+ return length
80
+ else
81
+ raise ArgumentError, "Invalid word length"
82
+ end
83
+ end
84
+
85
+ def get_word
86
+ return false if @data.empty?
87
+ length_size = bytes(get_length_size + 1)
88
+ length = get_length
89
+
90
+ total_size = length_size + length
91
+
92
+ if length && @data.size >= total_size
93
+ @data.slice!(0, length_size)
94
+ word = @data.slice!(0, length)
95
+ Mikrotik.debug [:parser, :got_word, word]
96
+ return word
97
+ end
98
+
99
+ return false
100
+ end
101
+
102
+ def get_sentence
103
+ while word = get_word
104
+ if word.empty?
105
+ Mikrotik.debug [:parser, :got_sentence, @sentence]
106
+ on_sentence(@sentence)
107
+ reset!
108
+ return true
109
+ else
110
+ if word =~ PAIR
111
+ key, value = word.scan(PAIR).flatten
112
+ @sentence[key] = value
113
+ else
114
+ @sentence[word] = nil
115
+ end
116
+ end
117
+ end
118
+
119
+ return false
120
+ end
121
+
122
+ def bytes(bits)
123
+ (bits / 8.0).ceil
124
+ end
125
+
126
+ end
@@ -0,0 +1,70 @@
1
+ class Mikrotik::Protocol::Sentence < Hash
2
+
3
+ # Sentence only contains a zero length word
4
+ # @return [Boolean]
5
+ def empty?
6
+ super || false
7
+ end
8
+
9
+ # Is this the last reply for a command?
10
+ # @return [Boolean]
11
+ def done?
12
+ key?('!done')
13
+ end
14
+
15
+ # Is this reply only informing that a command is done, and contains no other data?
16
+ # @return [Boolean]
17
+ def completely_done?
18
+ size == 2 and done? and tagged?
19
+ end
20
+
21
+ # Is this informing of an error that should be trapped?
22
+ # @return [Boolean]
23
+ def trap?
24
+ key?('!trap')
25
+ end
26
+
27
+ # Does the reply have a command tag associated with it?
28
+ # @return [Boolean]
29
+ def tagged?
30
+ key?('.tag')
31
+ end
32
+
33
+ # Retreives the reply's command tag
34
+ #
35
+ # @return [nil] If the command is not tagged
36
+ # @return [Integer] tag If the command is tagged
37
+ def tag
38
+ tagged? ? self['.tag'] : nil
39
+ end
40
+
41
+ # Fetches a returned property
42
+ # @return value
43
+ def result(key)
44
+ self["=#{key}"]
45
+ end
46
+
47
+ # Fetches any returned value in this reply, converting it to a ruby type first
48
+ def [](key)
49
+ return nil unless key?(key)
50
+ case key
51
+ when '.tag'
52
+ fetch(key).to_i
53
+ else
54
+ value = fetch(key)
55
+ #case value
56
+ #when /\d+/
57
+ # value.to_i
58
+ #when /\d+.\d+/
59
+ # value.to_f
60
+ #when 'true'
61
+ # true
62
+ #when 'false'
63
+ # false
64
+ #else
65
+ # value
66
+ #end
67
+ end
68
+ end
69
+
70
+ end
@@ -0,0 +1,22 @@
1
+ # @private
2
+ class Mikrotik::Utilities
3
+
4
+ def self.hex_to_bin(input)
5
+ padding = input.size.even? ? '' : '0'
6
+ [padding + input].pack('H*')
7
+ end
8
+
9
+ def self.bytepack(num, size)
10
+ s = String.new
11
+ s.force_encoding(Encoding::BINARY) if RUBY_VERSION >= '1.9.0'
12
+ x = num < 0 ? -num : num # Treat as unsigned
13
+ while size > 0
14
+ size -= 1
15
+ s = (x & 0xff).chr + s
16
+ x >>= 8
17
+ end
18
+ raise RuntimeError, "Number #{num} is too large to fit in #{size} bytes." if x > 0
19
+ return s
20
+ end
21
+
22
+ end
@@ -0,0 +1,3 @@
1
+ module Mikrotik
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "mikrotik/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "mikrotik"
7
+ s.version = Mikrotik::VERSION
8
+ s.authors = ["Iain Wilson"]
9
+ s.homepage = "http://github.com/ominiom/mikrotik"
10
+ s.summary = %q{A Mikrotik RouterOS API client using EventMachine}
11
+ s.description = %q{Provides an interface to run commands and collect results from a RouterOS device}
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
16
+ s.require_paths = ["lib"]
17
+ end
@@ -0,0 +1 @@
1
+ require './lib/mikrotik.rb'
@@ -0,0 +1,52 @@
1
+ require 'helper'
2
+
3
+ describe Mikrotik::Protocol::Parser do
4
+
5
+ before :each do
6
+ @parser = Mikrotik::Protocol::Parser.new
7
+ end
8
+
9
+ describe '#get_length_size' do
10
+
11
+ it "returns 7 for 1 byte lengths" do
12
+ @parser.instance_variable_set(:@data, 0b01111111.chr)
13
+ @parser.send(:get_length_size).should eq(7)
14
+ end
15
+
16
+ it "returns 14 for 2 byte lengths" do
17
+ @parser.instance_variable_set(:@data, 0b10111111.chr)
18
+ @parser.send(:get_length_size).should eq(14)
19
+ end
20
+
21
+ it "returns 21 for 3 byte lengths" do
22
+ @parser.instance_variable_set(:@data, 0b11011111.chr)
23
+ @parser.send(:get_length_size).should eq(21)
24
+ end
25
+
26
+ it "returns 28 for 28 bit lengths" do
27
+ @parser.instance_variable_set(:@data, 0b11101111.chr)
28
+ @parser.send(:get_length_size).should eq(28)
29
+ end
30
+
31
+ it "returns 32 for 32 bit lengths" do
32
+ @parser.instance_variable_set(:@data, 0b11110000.chr)
33
+ @parser.send(:get_length_size).should eq(32)
34
+ end
35
+
36
+ end
37
+
38
+ describe '#get_length' do
39
+
40
+ it "handles 7 bit lengths" do
41
+ @parser.instance_variable_set(:@data, 0b01111101.chr)
42
+ @parser.send(:get_length).should eq(0b1111101)
43
+ end
44
+
45
+ it "handles 14 bit lengths" do
46
+ @parser.instance_variable_set(:@data, 0b10111111.chr + 0b11111111.chr)
47
+ @parser.send(:get_length).should eq(63 * 256 + 255)
48
+ end
49
+
50
+ end
51
+
52
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mikrotik
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Iain Wilson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-18 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Provides an interface to run commands and collect results from a RouterOS
15
+ device
16
+ email:
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .yardopts
23
+ - Gemfile
24
+ - Guardfile
25
+ - README.markdown
26
+ - Rakefile
27
+ - doc/setup.rb
28
+ - examples/bandwidth_monitor.rb
29
+ - examples/dhcp.rb
30
+ - examples/uptime.rb
31
+ - lib/extensions/events.rb
32
+ - lib/extensions/string.rb
33
+ - lib/mikrotik.rb
34
+ - lib/mikrotik/client.rb
35
+ - lib/mikrotik/command.rb
36
+ - lib/mikrotik/connection.rb
37
+ - lib/mikrotik/errors.rb
38
+ - lib/mikrotik/protocol/parser.rb
39
+ - lib/mikrotik/protocol/sentence.rb
40
+ - lib/mikrotik/utilities.rb
41
+ - lib/mikrotik/version.rb
42
+ - mikrotik.gemspec
43
+ - spec/helper.rb
44
+ - spec/parser.rb
45
+ homepage: http://github.com/ominiom/mikrotik
46
+ licenses: []
47
+ post_install_message:
48
+ rdoc_options: []
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ requirements: []
64
+ rubyforge_project:
65
+ rubygems_version: 1.8.15
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: A Mikrotik RouterOS API client using EventMachine
69
+ test_files: []
70
+ has_rdoc: