pio 0.6.0 → 0.7.0

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/README.md +56 -22
  4. data/examples/dhcp_new.rb +22 -18
  5. data/examples/features_new.rb +14 -0
  6. data/examples/features_read.rb +4 -0
  7. data/features/arp_read.feature +4 -4
  8. data/features/dhcp_read.feature +2 -2
  9. data/features/echo_read.feature +5 -0
  10. data/features/features_read.feature +10 -0
  11. data/features/hello_read.feature +5 -0
  12. data/features/icmp_read.feature +2 -2
  13. data/features/lldp_read.feature +4 -4
  14. data/features/{pcap → packet_data}/arp-storm.pcap +0 -0
  15. data/features/{pcap → packet_data}/arp.pcap +0 -0
  16. data/features/{pcap → packet_data}/dhcp.pcap +0 -0
  17. data/features/packet_data/echo.raw +0 -0
  18. data/features/packet_data/features_reply.raw +0 -0
  19. data/features/packet_data/features_request.raw +0 -0
  20. data/features/packet_data/hello.raw +0 -0
  21. data/features/{pcap → packet_data}/icmp.pcap +0 -0
  22. data/features/{pcap → packet_data}/lldp.detailed.pcap +0 -0
  23. data/features/{pcap → packet_data}/lldp.minimal.pcap +0 -0
  24. data/features/step_definitions/packet_data_steps.rb +32 -0
  25. data/features/step_definitions/pending_steps.rb +5 -0
  26. data/lib/pio.rb +1 -0
  27. data/lib/pio/arp.rb +1 -1
  28. data/lib/pio/arp/{frame.rb → format.rb} +2 -2
  29. data/lib/pio/arp/message.rb +2 -2
  30. data/lib/pio/dhcp/dhcp_field.rb +3 -3
  31. data/lib/pio/dhcp/frame.rb +6 -6
  32. data/lib/pio/dhcp/optional_tlv.rb +3 -3
  33. data/lib/pio/echo.rb +4 -11
  34. data/lib/pio/echo/format.rb +5 -5
  35. data/lib/pio/echo/message.rb +13 -4
  36. data/lib/pio/echo/reply.rb +29 -0
  37. data/lib/pio/echo/request.rb +29 -0
  38. data/lib/pio/features.rb +18 -0
  39. data/lib/pio/features/format.rb +18 -0
  40. data/lib/pio/features/message.rb +14 -0
  41. data/lib/pio/features/reply.rb +73 -0
  42. data/lib/pio/features/request.rb +63 -0
  43. data/lib/pio/hello.rb +40 -9
  44. data/lib/pio/icmp.rb +1 -1
  45. data/lib/pio/icmp/{frame.rb → format.rb} +2 -2
  46. data/lib/pio/icmp/message.rb +2 -2
  47. data/lib/pio/icmp/request.rb +30 -14
  48. data/lib/pio/message_type_selector.rb +4 -7
  49. data/lib/pio/type/ethernet_header.rb +0 -2
  50. data/lib/pio/type/ipv4_header.rb +0 -1
  51. data/lib/pio/type/open_flow.rb +34 -0
  52. data/lib/pio/type/udp_header.rb +0 -1
  53. data/lib/pio/version.rb +1 -1
  54. data/pio.gemspec +2 -2
  55. data/spec/pio/dhcp/ack_spec.rb +1 -1
  56. data/spec/pio/dhcp_spec.rb +2 -2
  57. data/spec/pio/echo/reply_spec.rb +69 -4
  58. data/spec/pio/echo/request_spec.rb +48 -10
  59. data/spec/pio/echo_spec.rb +8 -0
  60. data/spec/pio/features/reply_spec.rb +30 -0
  61. data/spec/pio/features/request_spec.rb +70 -0
  62. data/spec/pio/features_spec.rb +78 -0
  63. data/spec/pio/hello_spec.rb +35 -6
  64. data/spec/spec_helper.rb +3 -0
  65. metadata +70 -40
  66. data/features/step_definitions/pcap_steps.rb +0 -18
@@ -24,29 +24,45 @@ module Pio
24
24
  option :sequence_number
25
25
  option :echo_data
26
26
 
27
- # rubocop:disable MethodLength
28
-
29
- def initialize(options)
30
- validate options
27
+ def initialize(user_options)
31
28
  @type = TYPE
32
- @source_mac = Mac.new(options[:source_mac]).freeze
33
- @destination_mac = Mac.new(options[:destination_mac]).freeze
29
+
30
+ @options = user_options.dup
31
+ validate @options
32
+ set_mac_and_ip_address_options
33
+ set_identifier_option
34
+ set_sequence_number_option
35
+ set_echo_data_option
36
+ end
37
+
38
+ private
39
+
40
+ def set_mac_and_ip_address_options
41
+ @source_mac = Mac.new(@options[:source_mac]).freeze
42
+ @destination_mac = Mac.new(@options[:destination_mac]).freeze
34
43
  @ip_source_address =
35
- IPv4Address.new(options[:ip_source_address]).freeze
44
+ IPv4Address.new(@options[:ip_source_address]).freeze
36
45
  @ip_destination_address =
37
- IPv4Address.new(options[:ip_destination_address]).freeze
46
+ IPv4Address.new(@options[:ip_destination_address]).freeze
47
+ end
48
+
49
+ def set_identifier_option
38
50
  @identifier =
39
- options[:icmp_identifier] ||
40
- options[:identifier] ||
51
+ @options[:icmp_identifier] ||
52
+ @options[:identifier] ||
41
53
  DEFAULT_IDENTIFIER
54
+ end
55
+
56
+ def set_sequence_number_option
42
57
  @sequence_number =
43
- options[:icmp_sequence_number] ||
44
- options[:sequence_number] ||
58
+ @options[:icmp_sequence_number] ||
59
+ @options[:sequence_number] ||
45
60
  DEFAULT_SEQUENCE_NUMBER
46
- @echo_data = options[:echo_data] || DEFAULT_ECHO_DATA
47
61
  end
48
62
 
49
- # rubocop:enable MethodLength
63
+ def set_echo_data_option
64
+ @echo_data = (@options[:echo_data] || DEFAULT_ECHO_DATA).freeze
65
+ end
50
66
  end
51
67
  end
52
68
  end
@@ -10,13 +10,10 @@ module Pio
10
10
  end
11
11
 
12
12
  def read(raw_data)
13
- begin
14
- frame = const_get(:Frame).read(raw_data)
15
- rescue
16
- raise Pio::ParseError, $ERROR_INFO.message
17
- end
18
-
19
- const_get(:MESSAGE_TYPE)[frame.message_type].create_from frame
13
+ parsed = const_get(:Format).read(raw_data)
14
+ const_get(:MESSAGE_TYPE)[parsed.message_type].create_from parsed
15
+ rescue
16
+ raise Pio::ParseError, $ERROR_INFO.message
20
17
  end
21
18
  end
22
19
  end
@@ -4,9 +4,7 @@ require 'pio/type/mac_address'
4
4
 
5
5
  module Pio
6
6
  module Type
7
- #
8
7
  # Adds ethernet_header macro.
9
- #
10
8
  module EthernetHeader
11
9
  def ethernet_header(options)
12
10
  class_eval do
@@ -8,7 +8,6 @@ module Pio
8
8
  module IPv4Header
9
9
  # rubocop:disable MethodLength
10
10
  def ipv4_header(options)
11
- endian :big
12
11
  class_eval do
13
12
  bit4 :ip_version, initial_value: 0x4
14
13
  bit4 :ip_header_length, initial_value: 0x5
@@ -0,0 +1,34 @@
1
+ # encoding: utf-8
2
+
3
+ require 'bindata'
4
+
5
+ module Pio
6
+ module Type
7
+ # OpenFlow 1.0 format.
8
+ module OpenFlow
9
+ def openflow_header
10
+ class_eval do
11
+ uint8 :version, value: 1
12
+ uint8 :message_type
13
+ uint16 :message_length, initial_value: -> { 8 + body.length }
14
+ uint32 :transaction_id, initial_value: 0
15
+ end
16
+ end
17
+
18
+ # Description of a physical port
19
+ class PhyPort < BinData::Record
20
+ endian :big
21
+
22
+ uint16 :port_no
23
+ uint8 :hw_addr
24
+ stringz :name, read_length: 16
25
+ uint32 :config
26
+ uint32 :state
27
+ uint32 :curr
28
+ uint32 :advertised
29
+ uint32 :supported
30
+ uint32 :peer
31
+ end
32
+ end
33
+ end
34
+ end
@@ -5,7 +5,6 @@ module Pio
5
5
  # UDP Header Format.
6
6
  module UdpHeader
7
7
  def udp_header(options)
8
- endian :big
9
8
  class_eval do
10
9
  uint16be :udp_src_port
11
10
  uint16be :udp_dst_port
@@ -3,5 +3,5 @@
3
3
  # Base module.
4
4
  module Pio
5
5
  # gem version.
6
- VERSION = '0.6.0'.freeze
6
+ VERSION = '0.7.0'.freeze
7
7
  end
@@ -35,6 +35,6 @@ Gem::Specification.new do |gem|
35
35
  gem.test_files += Dir.glob('features/**/*')
36
36
 
37
37
  gem.required_ruby_version = '>= 1.9.3'
38
- gem.add_dependency 'bindata', '~> 2.0.0'
39
- gem.add_development_dependency 'bundler', '~> 1.6.0'
38
+ gem.add_dependency 'bindata', '~> 2.1.0'
39
+ gem.add_development_dependency 'bundler', '~> 1.6.2'
40
40
  end
@@ -151,7 +151,7 @@ describe Pio::Dhcp::Ack, '.new' do
151
151
  end
152
152
  end
153
153
 
154
- context 'with String MAC Address' do
154
+ context 'with String MAC Address' do
155
155
  let(:source_mac) { 'aa:bb:cc:dd:ee:ff' }
156
156
  let(:destination_mac) { '11:22:33:44:55:66' }
157
157
  let(:ip_source_address) { '192.168.0.10' }
@@ -152,7 +152,7 @@ describe Pio::Dhcp, '.read' do
152
152
  its('client_mac_address.to_s') { should eq '24:db:ac:41:e5:5b' }
153
153
  its('client_identifier.to_s') { should eq '24:db:ac:41:e5:5b' }
154
154
  its('requested_ip_address.to_s') { should eq '0.0.0.0' }
155
- its(:parameters_list) { should eq [0x01, 0x03, 0x06, 0x2a] }
155
+ its(:parameters_list) { should eq [0x01, 0x03, 0x06, 0x2a] }
156
156
  end
157
157
 
158
158
  context 'with DHCP offer frame' do
@@ -452,7 +452,7 @@ describe Pio::Dhcp, '.read' do
452
452
  its('client_mac_address.to_s') { should eq '24:db:ac:41:e5:5b' }
453
453
  its('client_identifier.to_s') { should eq '24:db:ac:41:e5:5b' }
454
454
  its('requested_ip_address.to_s') { should eq '192.168.0.10' }
455
- its(:parameters_list) { should eq [0x01, 0x03, 0x06, 0x2a] }
455
+ its(:parameters_list) { should eq [0x01, 0x03, 0x06, 0x2a] }
456
456
  end
457
457
 
458
458
  context 'with DHCP ACK frame' do
@@ -4,17 +4,82 @@ require 'pio'
4
4
 
5
5
  describe Pio::Echo::Reply do
6
6
  describe '.new' do
7
- context 'with no additional data' do
8
- When(:echo_reply) do
9
- Pio::Echo::Reply.new(xid: 123)
7
+ context 'with no arguments' do
8
+ When(:echo_reply) { Pio::Echo::Reply.new }
9
+
10
+ Then { echo_reply.version == 1 }
11
+ Then { echo_reply.message_type == Pio::Echo::REPLY }
12
+ Then { echo_reply.message_length == 8 }
13
+ Then { echo_reply.transaction_id == 0 }
14
+ Then { echo_reply.xid == 0 }
15
+ Then { echo_reply.data == '' }
16
+ Then { echo_reply.to_binary == [1, 3, 0, 8, 0, 0, 0, 0].pack('C*') }
17
+ end
18
+
19
+ context 'with 123' do
20
+ When(:echo_reply) { Pio::Echo::Reply.new(123) }
21
+
22
+ Then { echo_reply.version == 1 }
23
+ Then { echo_reply.message_type == Pio::Echo::REPLY }
24
+ Then { echo_reply.message_length == 8 }
25
+ Then { echo_reply.transaction_id == 123 }
26
+ Then { echo_reply.xid == 123 }
27
+ Then { echo_reply.data == '' }
28
+ Then { echo_reply.to_binary == [1, 3, 0, 8, 0, 0, 0, 123].pack('C*') }
29
+ end
30
+
31
+ context 'with 2**32' do
32
+ When(:result) { Pio::Echo::Reply.new(2**32) }
33
+
34
+ Then do
35
+ pending 'check if xid is within 32bit range.'
36
+ result == Failure(ArgumentError)
10
37
  end
38
+ end
39
+
40
+ context 'with transaction_id: 123' do
41
+ When(:echo_reply) { Pio::Echo::Reply.new(transaction_id: 123) }
11
42
 
12
- Then { echo_reply.class == Pio::Echo::Reply }
13
43
  Then { echo_reply.version == 1 }
14
44
  Then { echo_reply.message_type == Pio::Echo::REPLY }
15
45
  Then { echo_reply.message_length == 8 }
46
+ Then { echo_reply.transaction_id == 123 }
16
47
  Then { echo_reply.xid == 123 }
17
48
  Then { echo_reply.data == '' }
49
+ Then { echo_reply.to_binary == [1, 3, 0, 8, 0, 0, 0, 123].pack('C*') }
50
+ end
51
+
52
+ context 'with xid: 123' do
53
+ When(:echo_reply) { Pio::Echo::Reply.new(xid: 123) }
54
+
55
+ Then { echo_reply.version == 1 }
56
+ Then { echo_reply.message_type == Pio::Echo::REPLY }
57
+ Then { echo_reply.message_length == 8 }
58
+ Then { echo_reply.transaction_id == 123 }
59
+ Then { echo_reply.xid == 123 }
60
+ Then { echo_reply.data == '' }
61
+ Then { echo_reply.to_binary == [1, 3, 0, 8, 0, 0, 0, 123].pack('C*') }
62
+ end
63
+
64
+ context "with transaction_id: 123, data: 'foobar'" do
65
+ When(:echo_reply) { Pio::Echo::Reply.new(xid: 123, data: 'foobar') }
66
+
67
+ Then { echo_reply.version == 1 }
68
+ Then { echo_reply.message_type == Pio::Echo::REPLY }
69
+ Then { echo_reply.message_length == 14 }
70
+ Then { echo_reply.transaction_id == 123 }
71
+ Then { echo_reply.xid == 123 }
72
+ Then { echo_reply.data == 'foobar' }
73
+ Then do
74
+ echo_reply.to_binary ==
75
+ [1, 3, 0, 14, 0, 0, 0, 123, 102, 111, 111, 98, 97, 114].pack('C*')
76
+ end
77
+ end
78
+
79
+ context 'with :INVALID_ARGUMENT' do
80
+ When(:result) { Pio::Echo::Reply.new(:INVALID_ARGUMENT) }
81
+
82
+ Then { result == Failure(TypeError) }
18
83
  end
19
84
  end
20
85
  end
@@ -4,44 +4,82 @@ require 'pio'
4
4
 
5
5
  describe Pio::Echo::Request do
6
6
  describe '.new' do
7
- Given(:echo_request) do
8
- Pio::Echo::Request.new(user_options)
9
- end
10
-
11
7
  context 'with no arguments' do
12
- When(:user_options) { {} }
8
+ When(:echo_request) { Pio::Echo::Request.new }
13
9
 
14
- Then { echo_request.class == Pio::Echo::Request }
15
10
  Then { echo_request.version == 1 }
16
11
  Then { echo_request.message_type == Pio::Echo::REQUEST }
17
12
  Then { echo_request.message_length == 8 }
18
13
  Then { echo_request.transaction_id == 0 }
19
14
  Then { echo_request.xid == 0 }
20
15
  Then { echo_request.data == '' }
16
+ Then { echo_request.to_binary == [1, 2, 0, 8, 0, 0, 0, 0].pack('C*') }
17
+ end
18
+
19
+ context 'with 123' do
20
+ When(:echo_request) { Pio::Echo::Request.new(123) }
21
+
22
+ Then { echo_request.version == 1 }
23
+ Then { echo_request.message_type == Pio::Echo::REQUEST }
24
+ Then { echo_request.message_length == 8 }
25
+ Then { echo_request.transaction_id == 123 }
26
+ Then { echo_request.xid == 123 }
27
+ Then { echo_request.data == '' }
28
+ Then { echo_request.to_binary == [1, 2, 0, 8, 0, 0, 0, 123].pack('C*') }
29
+ end
30
+
31
+ context 'with 2**32' do
32
+ When(:result) { Pio::Echo::Request.new(2**32) }
33
+
34
+ Then do
35
+ pending 'check if xid is within 32bit range.'
36
+ result == Failure(ArgumentError)
37
+ end
21
38
  end
22
39
 
23
40
  context 'with transaction_id: 123' do
24
- When(:user_options) { { transaction_id: 123 } }
41
+ When(:echo_request) { Pio::Echo::Request.new(transaction_id: 123) }
25
42
 
26
- Then { echo_request.class == Pio::Echo::Request }
27
43
  Then { echo_request.version == 1 }
28
44
  Then { echo_request.message_type == Pio::Echo::REQUEST }
29
45
  Then { echo_request.message_length == 8 }
30
46
  Then { echo_request.transaction_id == 123 }
31
47
  Then { echo_request.xid == 123 }
32
48
  Then { echo_request.data == '' }
49
+ Then { echo_request.to_binary == [1, 2, 0, 8, 0, 0, 0, 123].pack('C*') }
33
50
  end
34
51
 
35
52
  context 'with xid: 123' do
36
- When(:user_options) { { xid: 123 } }
53
+ When(:echo_request) { Pio::Echo::Request.new(xid: 123) }
37
54
 
38
- Then { echo_request.class == Pio::Echo::Request }
39
55
  Then { echo_request.version == 1 }
40
56
  Then { echo_request.message_type == Pio::Echo::REQUEST }
41
57
  Then { echo_request.message_length == 8 }
42
58
  Then { echo_request.transaction_id == 123 }
43
59
  Then { echo_request.xid == 123 }
44
60
  Then { echo_request.data == '' }
61
+ Then { echo_request.to_binary == [1, 2, 0, 8, 0, 0, 0, 123].pack('C*') }
62
+ end
63
+
64
+ context "with transaction_id: 123, data: 'foobar'" do
65
+ When(:echo_request) { Pio::Echo::Request.new(xid: 123, data: 'foobar') }
66
+
67
+ Then { echo_request.version == 1 }
68
+ Then { echo_request.message_type == Pio::Echo::REQUEST }
69
+ Then { echo_request.message_length == 14 }
70
+ Then { echo_request.transaction_id == 123 }
71
+ Then { echo_request.xid == 123 }
72
+ Then { echo_request.data == 'foobar' }
73
+ Then do
74
+ echo_request.to_binary ==
75
+ [1, 2, 0, 14, 0, 0, 0, 123, 102, 111, 111, 98, 97, 114].pack('C*')
76
+ end
77
+ end
78
+
79
+ context 'with :INVALID_ARGUMENT' do
80
+ When(:result) { Pio::Echo::Request.new(:INVALID_ARGUMENT) }
81
+
82
+ Then { result == Failure(TypeError) }
45
83
  end
46
84
  end
47
85
  end
@@ -39,5 +39,13 @@ describe Pio::Echo do
39
39
  Then { echo_reply.data == '' }
40
40
  Then { echo_reply.to_binary == echo_reply_dump }
41
41
  end
42
+
43
+ context 'with a Features Request message' do
44
+ Given(:features_request_dump) { [1, 5, 0, 8, 0, 0, 0, 0].pack('C*') }
45
+
46
+ When(:result) { Pio::Echo.read(features_request_dump) }
47
+
48
+ Then { result == Failure(Pio::ParseError) }
49
+ end
42
50
  end
43
51
  end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pio'
4
+
5
+ describe Pio::Features::Reply do
6
+ describe '.new' do
7
+ Given(:options) do
8
+ {
9
+ dpid: 0x123,
10
+ n_buffers: 0x100,
11
+ n_tables: 0xfe,
12
+ capabilities: 0xc7,
13
+ actions: 0xfff
14
+ }
15
+ end
16
+
17
+ When(:features_reply) { Pio::Features::Reply.new(options) }
18
+
19
+ Then { features_reply.version == 1 }
20
+ Then { features_reply.message_type == Pio::Features::REPLY }
21
+ Then { features_reply.transaction_id == 0 }
22
+ Then { features_reply.xid == 0 }
23
+ Then { features_reply.dpid == 0x123 }
24
+ Then { features_reply.n_buffers == 0x100 }
25
+ Then { features_reply.n_tables == 0xfe }
26
+ Then { features_reply.capabilities == 0xc7 }
27
+ Then { features_reply.actions == 0xfff }
28
+ Then { features_reply.ports == [] }
29
+ end
30
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+
3
+ require 'pio'
4
+
5
+ describe Pio::Features::Request do
6
+ describe '.new' do
7
+ context 'with no arguments' do
8
+ When(:request) { Pio::Features::Request.new }
9
+
10
+ Then { request.version == 1 }
11
+ Then { request.message_type == Pio::Features::REQUEST }
12
+ Then { request.message_length == 8 }
13
+ Then { request.transaction_id == 0 }
14
+ Then { request.xid == 0 }
15
+ Then { request.body == '' }
16
+ Then { request.to_binary == [1, 5, 0, 8, 0, 0, 0, 0].pack('C*') }
17
+ end
18
+
19
+ context 'with 123' do
20
+ When(:request) { Pio::Features::Request.new(123) }
21
+
22
+ Then { request.version == 1 }
23
+ Then { request.message_type == Pio::Features::REQUEST }
24
+ Then { request.message_length == 8 }
25
+ Then { request.transaction_id == 123 }
26
+ Then { request.xid == 123 }
27
+ Then { request.body == '' }
28
+ Then { request.to_binary == [1, 5, 0, 8, 0, 0, 0, 123].pack('C*') }
29
+ end
30
+
31
+ context 'with 2**32' do
32
+ When(:result) { Pio::Features::Request.new(2**32) }
33
+
34
+ Then do
35
+ pending 'check if xid is within 32bit range.'
36
+ result == Failure(ArgumentError)
37
+ end
38
+ end
39
+
40
+ context 'with transaction_id: 123' do
41
+ When(:request) { Pio::Features::Request.new(transaction_id: 123) }
42
+
43
+ Then { request.version == 1 }
44
+ Then { request.message_type == Pio::Features::REQUEST }
45
+ Then { request.message_length == 8 }
46
+ Then { request.transaction_id == 123 }
47
+ Then { request.xid == 123 }
48
+ Then { request.body == '' }
49
+ Then { request.to_binary == [1, 5, 0, 8, 0, 0, 0, 123].pack('C*') }
50
+ end
51
+
52
+ context 'with xid: 123' do
53
+ When(:request) { Pio::Features::Request.new(xid: 123) }
54
+
55
+ Then { request.version == 1 }
56
+ Then { request.message_type == Pio::Features::REQUEST }
57
+ Then { request.message_length == 8 }
58
+ Then { request.transaction_id == 123 }
59
+ Then { request.xid == 123 }
60
+ Then { request.body == '' }
61
+ Then { request.to_binary == [1, 5, 0, 8, 0, 0, 0, 123].pack('C*') }
62
+ end
63
+
64
+ context 'with :INVALID_ARGUMENT' do
65
+ When(:result) { Pio::Features::Request.new(:INVALID_ARGUMENT) }
66
+
67
+ Then { result == Failure(TypeError) }
68
+ end
69
+ end
70
+ end