Skip to content

Commit

Permalink
Added Pio::Hello13
Browse files Browse the repository at this point in the history
  • Loading branch information
yasuhito committed Apr 7, 2015
1 parent c2a7536 commit 32c3f14
Show file tree
Hide file tree
Showing 10 changed files with 285 additions and 18 deletions.
2 changes: 2 additions & 0 deletions .reek
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
UncommunicativeModuleName:
accept: ['Pio::Hello13']
28 changes: 28 additions & 0 deletions features/hello13_read.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Feature: Pio::Hello13.read
Scenario: hello13_no_version_bitmap.raw
Given a packet data file "hello13_no_version_bitmap.raw"
When I try to parse the file with "Hello13" class
Then it should finish successfully
And the parsed data have the following field and value:
| field | value |
| class | Pio::Hello13 |
| ofp_version | 4 |
| message_type | 0 |
| message_length | 8 |
| transaction_id | 0 |
| xid | 0 |
| supported_versions | [] |

Scenario: hello13_version_bitmap.raw
Given a packet data file "hello13_version_bitmap.raw"
When I try to parse the file with "Hello13" class
Then it should finish successfully
And the parsed data have the following field and value:
| field | value |
| class | Pio::Hello13 |
| ofp_version | 4 |
| message_type | 0 |
| message_length | 16 |
| transaction_id | 0 |
| xid | 0 |
| supported_versions | [:open_flow10, :open_flow13] |
Binary file not shown.
Binary file added features/packet_data/hello13_version_bitmap.raw
Binary file not shown.
1 change: 1 addition & 0 deletions lib/pio.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require 'pio/features'
require 'pio/flow_mod'
require 'pio/hello'
require 'pio/hello13'
require 'pio/icmp'
require 'pio/lldp'
require 'pio/mac'
Expand Down
113 changes: 113 additions & 0 deletions lib/pio/hello13.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
require 'bindata'
require 'pio/open_flow/transaction_id'
require 'pio/parse_error'

module Pio
# OpenFlow 1.3 Hello message parser and generator
class Hello13
# ofp_hello_elem_header and value
class Element < BinData::Record
VERSION_BITMAP = 1

endian :big

uint16 :element_type
uint16 :element_length
choice :element_value, selection: :chooser do
string 'unknown', read_length: -> { element_length - 4 }
uint32 'version_bitmap'
end

def version_bitmap?
element_type == VERSION_BITMAP
end

private

def chooser
version_bitmap? ? 'version_bitmap' : 'unknown'
end
end

# OpenFlow 1.3 Hello message format
class Format < BinData::Record
OFP_VERSION = 4

endian :big

uint8 :ofp_version, initial_value: OFP_VERSION
virtual assert: -> { ofp_version == OFP_VERSION }
uint8 :message_type
uint16 :message_length, initial_value: :hello_message_length
transaction_id :transaction_id
array :elements, type: :element, read_until: :eof

def xid
transaction_id
end

def supported_versions
supported_versions_list.map do |each|
"open_flow1#{each - 1}".to_sym
end
end

alias_method :to_binary, :to_binary_s

private

def hello_message_length
8 + if elements.empty?
0
else
elements.length * 4 + 4
end
end

def supported_versions_list
(1..32).each_with_object([]) do |each, result|
result << each if (version_bitmap >> each & 1) == 1
end
end

def version_bitmap
bitmap = elements.select(&:version_bitmap?).first
bitmap ? bitmap.element_value : 0
end
end

# Hello13.new argument
class Options
def initialize(user_attrs)
@attrs = { elements: [{ element_type: 1,
element_length: 8,
element_value: 16 }] }
@attrs = @attrs.merge(user_attrs)
@attrs[:transaction_id] = @attrs.fetch(:xid) if @attrs.key?(:xid)
unknown_keywords = @attrs.keys - [:transaction_id, :xid, :elements]
return if unknown_keywords.empty?
fail "Unknown keyword: #{unknown_keywords.first}"
end

def to_h
@attrs
end
end

def self.read(raw_data)
allocate.tap do |message|
message.instance_variable_set(:@format, Format.read(raw_data))
end
rescue BinData::ValidityError
raise Pio::ParseError, 'Invalid Hello 1.3 message.'
end

def initialize(attrs = {})
@format = Format.new(Options.new(attrs).to_h)
end

def method_missing(method, *args, &block)
@format.__send__ method, *args, &block
end
end
end
19 changes: 1 addition & 18 deletions lib/pio/open_flow/open_flow_header.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,11 @@
require 'bindata'
require 'pio/open_flow/transaction_id'

module Pio
# OpenFlow 1.0 format.
module OpenFlow
# OpenFlow 1.0 message header format.
class OpenFlowHeader < BinData::Record
# Transaction ID (uint32)
class TransactionId < BinData::Primitive
endian :big
uint32 :xid

def set(value)
unless value.unsigned_32bit?
fail(ArgumentError,
'Transaction ID should be an unsigned 32-bit integer.')
end
self.xid = value
end

def get
xid
end
end

endian :big
uint8 :ofp_version, value: 1
uint8 :message_type, initial_value: :message_type_value
Expand Down
25 changes: 25 additions & 0 deletions lib/pio/open_flow/transaction_id.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
require 'bindata'
require 'pio/monkey_patch/integer'

module Pio
module OpenFlow
# Transaction ID (uint32)
class TransactionId < BinData::Primitive
endian :big

uint32 :xid

def set(value)
unless value.unsigned_32bit?
fail(ArgumentError,
'Transaction ID should be an unsigned 32-bit integer.')
end
self.xid = value
end

def get
xid
end
end
end
end
114 changes: 114 additions & 0 deletions spec/pio/hello13_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
require 'pio/hello13'

describe Pio::Hello13 do
describe '.read' do
When(:result) { Pio::Hello13.read(binary) }

context 'with a hello message (no version bitmap)' do
Given(:binary) { [4, 0, 0, 8, 0, 0, 0, 0].pack('C*') }

Then { result.class == Pio::Hello13 }
Then { result.ofp_version == 4 }
Then { result.message_type == 0 }
Then { result.message_length == 8 }
Then { result.transaction_id == 0 }
Then { result.xid == 0 }
Then { result.supported_versions.empty? }
end

context 'with a hello message' do
Given(:binary) do
[4, 0, 0, 16, 0, 0, 0, 0, 0, 1, 0, 8, 0, 0, 0, 18].pack('C*')
end

Then { result.class == Pio::Hello13 }
Then { result.ofp_version == 4 }
Then { result.message_type == 0 }
Then { result.message_length == 16 }
Then { result.transaction_id == 0 }
Then { result.xid == 0 }
Then { result.supported_versions == [:open_flow10, :open_flow13] }
end

context 'with a hello message (OpenFlow 1.0)' do
Given(:binary) { [1, 0, 0, 8, 0, 0, 0, 0].pack('C*') }

Then { result == Failure(Pio::ParseError, 'Invalid Hello 1.3 message.') }
end
end

describe '.new' do
context 'with no arguments' do
When(:result) { Pio::Hello13.new }

Then { result.ofp_version == 4 }
Then { result.message_type == 0 }
Then { result.message_length == 16 }
Then { result.transaction_id == 0 }
Then { result.xid == 0 }
Then { result.supported_versions == [:open_flow13] }
Then do
result.to_binary ==
[4, 0, 0, 16, 0, 0, 0, 0, 0, 1, 0, 8, 0, 0, 0, 16].pack('C*')
end
end

context 'with transaction_id: 123' do
When(:result) { Pio::Hello13.new(transaction_id: 123) }

Then { result.ofp_version == 4 }
Then { result.message_type == 0 }
Then { result.message_length == 16 }
Then { result.transaction_id == 123 }
Then { result.xid == 123 }
Then { result.supported_versions == [:open_flow13] }
Then do
result.to_binary ==
[4, 0, 0, 16, 0, 0, 0, 123, 0, 1, 0, 8, 0, 0, 0, 16].pack('C*')
end
end

context 'with xid: 123' do
When(:result) { Pio::Hello13.new(xid: 123) }

Then { result.ofp_version == 4 }
Then { result.message_type == 0 }
Then { result.message_length == 16 }
Then { result.transaction_id == 123 }
Then { result.xid == 123 }
Then { result.supported_versions == [:open_flow13] }
Then do
result.to_binary ==
[4, 0, 0, 16, 0, 0, 0, 123, 0, 1, 0, 8, 0, 0, 0, 16].pack('C*')
end
end

context 'with xid: -1' do
When(:result) { Pio::Hello13.new(xid: -1) }

Then do
result ==
Failure(ArgumentError,
'Transaction ID should be an unsigned 32-bit integer.')
end
end

context 'with xid: 2**32' do
When(:result) { Pio::Hello13.new(xid: 2**32) }

Then do
result ==
Failure(ArgumentError,
'Transaction ID should be an unsigned 32-bit integer.')
end
end

context 'with invalid_keyword: :foo' do
When(:result) { Pio::Hello13.new(invalid_keyword: :foo) }

Then do
result == Failure(RuntimeError, 'Unknown keyword: invalid_keyword')
end
end
end
end
1 change: 1 addition & 0 deletions tasks/reek.rake
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
begin
require 'reek/rake/task'
Reek::Rake::Task.new do |t|
t.config_file = File.join(__dir__, '..', '.reek')
t.fail_on_error = false
t.verbose = false
end
Expand Down

0 comments on commit 32c3f14

Please sign in to comment.