pio 0.6.0 → 0.7.0

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