mikrotik 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: