pio 0.25.0 → 0.26.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 (134) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/README.md +4 -0
  4. data/Rakefile +1 -1
  5. data/bin/terminal-notifier +16 -0
  6. data/features/open_flow10/bad_request.feature +35 -0
  7. data/features/open_flow10/bad_request.raw +0 -0
  8. data/features/open_flow10/barrier_reply.feature +4 -4
  9. data/features/open_flow10/barrier_request.feature +4 -4
  10. data/features/open_flow10/echo_reply.feature +13 -13
  11. data/features/open_flow10/echo_request.feature +6 -6
  12. data/features/open_flow10/enqueue.feature +17 -0
  13. data/features/open_flow10/features_reply.feature +2 -2
  14. data/features/open_flow10/features_request.feature +28 -28
  15. data/features/open_flow10/flow_mod.feature +5 -5
  16. data/features/open_flow10/flow_stats_reply.feature +12 -12
  17. data/features/open_flow10/flow_stats_request.feature +3 -3
  18. data/features/open_flow10/hello.feature +8 -15
  19. data/features/open_flow10/hello_failed.feature +69 -0
  20. data/features/open_flow10/hello_failed.raw +0 -0
  21. data/features/open_flow10/nxast_learn.raw +0 -0
  22. data/features/open_flow10/packet_in.feature +2 -2
  23. data/features/open_flow10/packet_out.feature +13 -13
  24. data/features/open_flow10/port_status.feature +1 -1
  25. data/features/open_flow10/send_out_port.feature +106 -0
  26. data/features/open_flow10/set_ether_destination_address.feature +16 -0
  27. data/features/open_flow10/set_ether_source_address.feature +16 -0
  28. data/features/open_flow10/set_ip_destination_address.feature +16 -0
  29. data/features/open_flow10/set_ip_source_address.feature +16 -0
  30. data/features/open_flow10/set_ip_tos.feature +16 -0
  31. data/features/open_flow10/set_transport_destination_port.feature +16 -0
  32. data/features/open_flow10/set_transport_source_port.feature +16 -0
  33. data/features/open_flow10/set_vlan_priority.feature +16 -0
  34. data/features/open_flow10/set_vlan_vid.feature +16 -0
  35. data/features/open_flow10/strip_vlan_header.feature +15 -0
  36. data/features/open_flow10/vendor_action.feature +14 -0
  37. data/features/open_flow13/bad_request.feature +35 -0
  38. data/features/open_flow13/bad_request.raw +0 -0
  39. data/features/open_flow13/echo_reply.feature +7 -7
  40. data/features/open_flow13/echo_request.feature +7 -7
  41. data/features/open_flow13/features_reply.feature +2 -2
  42. data/features/open_flow13/features_request.feature +28 -28
  43. data/features/open_flow13/flow_mod.feature +24 -24
  44. data/features/open_flow13/hello.feature +5 -5
  45. data/features/open_flow13/hello_failed.feature +69 -0
  46. data/features/open_flow13/hello_failed.raw +0 -0
  47. data/features/open_flow13/packet_in.feature +3 -3
  48. data/features/open_flow13/packet_out.feature +3 -3
  49. data/features/open_flow13/send_out_port.feature +101 -10
  50. data/features/open_flow13/table_stats_reply.raw +0 -0
  51. data/features/open_flow13/table_stats_request.raw +0 -0
  52. data/features/open_flow_read.feature +6 -0
  53. data/features/step_definitions/Gemfile +9 -0
  54. data/features/step_definitions/Gemfile.lock +76 -0
  55. data/features/step_definitions/Guardfile +4 -0
  56. data/features/step_definitions/LICENSE +675 -0
  57. data/features/step_definitions/README.md +7 -0
  58. data/features/step_definitions/Rakefile +10 -0
  59. data/features/step_definitions/dump_flows_steps.rb +13 -0
  60. data/features/step_definitions/packet_data_steps.rb +4 -0
  61. data/features/step_definitions/rest_api_steps.rb +40 -0
  62. data/features/step_definitions/show_stats_steps.rb +83 -0
  63. data/features/step_definitions/trema_steps.rb +33 -0
  64. data/features/step_definitions/virtual_link_steps.rb +8 -0
  65. data/lib/pio/open_flow.rb +5 -5
  66. data/lib/pio/open_flow/echo.rb +1 -1
  67. data/lib/pio/open_flow/error.rb +19 -0
  68. data/lib/pio/open_flow/format.rb +2 -1
  69. data/lib/pio/open_flow/hello_failed_code.rb +21 -0
  70. data/lib/pio/open_flow/open_flow_header.rb +11 -2
  71. data/lib/pio/open_flow/port.rb +69 -0
  72. data/lib/pio/open_flow10.rb +5 -0
  73. data/lib/pio/open_flow10/actions.rb +8 -6
  74. data/lib/pio/open_flow10/enqueue.rb +13 -13
  75. data/lib/pio/open_flow10/error.rb +28 -0
  76. data/lib/pio/open_flow10/error/bad_request.rb +66 -0
  77. data/lib/pio/open_flow10/error/error_type10.rb +26 -0
  78. data/lib/pio/open_flow10/error/hello_failed.rb +41 -0
  79. data/lib/pio/open_flow10/features.rb +6 -10
  80. data/lib/pio/open_flow10/flow_mod.rb +1 -1
  81. data/lib/pio/open_flow10/flow_stats_request.rb +1 -1
  82. data/lib/pio/open_flow10/hello.rb +2 -2
  83. data/lib/pio/open_flow10/match.rb +2 -2
  84. data/lib/pio/{open_flow/phy_port.rb → open_flow10/phy_port16.rb} +4 -4
  85. data/lib/pio/open_flow10/port16.rb +21 -0
  86. data/lib/pio/open_flow10/port_status.rb +1 -1
  87. data/lib/pio/open_flow10/send_out_port.rb +16 -40
  88. data/lib/pio/open_flow10/set_ether_address.rb +9 -8
  89. data/lib/pio/open_flow10/set_ip_address.rb +4 -4
  90. data/lib/pio/open_flow10/set_ip_tos.rb +4 -4
  91. data/lib/pio/open_flow10/set_transport_port.rb +12 -12
  92. data/lib/pio/open_flow10/set_vlan.rb +4 -4
  93. data/lib/pio/open_flow10/set_vlan_vid.rb +0 -12
  94. data/lib/pio/open_flow10/strip_vlan_header.rb +7 -7
  95. data/lib/pio/open_flow10/vendor_action.rb +33 -0
  96. data/lib/pio/open_flow13.rb +3 -0
  97. data/lib/pio/open_flow13/actions.rb +3 -3
  98. data/lib/pio/open_flow13/error.rb +28 -0
  99. data/lib/pio/open_flow13/error/bad_request.rb +66 -0
  100. data/lib/pio/open_flow13/error/error_type13.rb +37 -0
  101. data/lib/pio/open_flow13/error/hello_failed.rb +42 -0
  102. data/lib/pio/open_flow13/features_reply.rb +33 -33
  103. data/lib/pio/open_flow13/features_request.rb +3 -3
  104. data/lib/pio/open_flow13/flow_mod.rb +2 -2
  105. data/lib/pio/open_flow13/goto_table.rb +2 -0
  106. data/lib/pio/open_flow13/packet_out.rb +1 -1
  107. data/lib/pio/open_flow13/port32.rb +21 -0
  108. data/lib/pio/open_flow13/send_out_port.rb +3 -2
  109. data/lib/pio/version.rb +1 -1
  110. data/pio.gemspec +5 -5
  111. data/spec/pio/open_flow10/enqueue_spec.rb +22 -22
  112. data/spec/pio/open_flow10/error/hello_failed_spec.rb +26 -0
  113. data/spec/pio/open_flow10/flow_mod_spec.rb +6 -6
  114. data/spec/pio/open_flow10/flow_stats_request_spec.rb +1 -1
  115. data/spec/pio/open_flow10/hello_spec.rb +3 -3
  116. data/spec/pio/open_flow10/packet_out_spec.rb +33 -33
  117. data/spec/pio/{open_flow/phy_port_spec.rb → open_flow10/phy_port16_spec.rb} +7 -7
  118. data/spec/pio/open_flow10/send_out_port_spec.rb +28 -28
  119. data/spec/pio/open_flow10/set_ether_destination_address_spec.rb +2 -2
  120. data/spec/pio/open_flow10/set_ether_source_address_spec.rb +2 -2
  121. data/spec/pio/open_flow10/set_ip_destination_address_spec.rb +4 -4
  122. data/spec/pio/open_flow10/set_ip_source_address_spec.rb +4 -4
  123. data/spec/pio/open_flow10/set_ip_tos_spec.rb +4 -4
  124. data/spec/pio/open_flow10/set_transport_destination_port_spec.rb +11 -11
  125. data/spec/pio/open_flow10/set_transport_source_port_spec.rb +11 -11
  126. data/spec/pio/open_flow10/set_vlan_priority_spec.rb +4 -4
  127. data/spec/pio/open_flow10/set_vlan_vid_spec.rb +4 -4
  128. data/spec/pio/open_flow10/strip_vlan_header_spec.rb +6 -6
  129. data/spec/pio/open_flow13/error/bad_request_spec.rb +6 -0
  130. data/spec/pio/open_flow13/error/hello_failed_spec.rb +26 -0
  131. data/spec/pio/open_flow13/hello_spec.rb +3 -3
  132. data/spec/support/shared_examples_for_openflow_messages.rb +40 -27
  133. metadata +106 -16
  134. data/lib/pio/open_flow/port_number.rb +0 -39
@@ -6,13 +6,13 @@ module Pio
6
6
  # An action to modify the source/destination Ethernet address of a packet.
7
7
  class SetEtherAddress
8
8
  # rubocop:disable MethodLength
9
- def self.def_format(action_type)
9
+ def self.action_type(action_type)
10
10
  str = %(
11
11
  class Format < BinData::Record
12
12
  endian :big
13
13
 
14
- uint16 :type, value: #{action_type}
15
- uint16 :message_length, value: 16
14
+ uint16 :action_type, value: #{action_type}
15
+ uint16 :action_length, value: 16
16
16
  mac_address :mac_address
17
17
  uint48 :padding
18
18
  hide :padding
@@ -31,7 +31,8 @@ module Pio
31
31
 
32
32
  extend Forwardable
33
33
 
34
- def_delegators :@format, :message_length
34
+ def_delegators :@format, :action_type
35
+ def_delegator :@format, :action_length, :length
35
36
  def_delegators :@format, :mac_address
36
37
  def_delegator :@format, :to_binary_s, :to_binary
37
38
 
@@ -41,12 +42,12 @@ module Pio
41
42
  end
42
43
 
43
44
  # An action to modify the source Ethernet address of a packet.
44
- class SetEtherSourceAddr < SetEtherAddress
45
- def_format 4
45
+ class SetEtherSourceAddress < SetEtherAddress
46
+ action_type 4
46
47
  end
47
48
 
48
49
  # An action to modify the destination Ethernet address of a packet.
49
- class SetEtherDestinationAddr < SetEtherAddress
50
- def_format 5
50
+ class SetEtherDestinationAddress < SetEtherAddress
51
+ action_type 5
51
52
  end
52
53
  end
@@ -10,8 +10,8 @@ module Pio
10
10
  class Format < BinData::Record
11
11
  endian :big
12
12
 
13
- uint16 :type, value: #{action_type}
14
- uint16 :message_length, value: 8
13
+ uint16 :action_type, value: #{action_type}
14
+ uint16 :action_length, value: 8
15
15
  ip_address :ip_address
16
16
  end
17
17
  )
@@ -27,8 +27,8 @@ module Pio
27
27
 
28
28
  extend Forwardable
29
29
 
30
- def_delegators :@format, :type
31
- def_delegators :@format, :message_length
30
+ def_delegators :@format, :action_type
31
+ def_delegator :@format, :action_length, :length
32
32
  def_delegators :@format, :ip_address
33
33
  def_delegator :@format, :to_binary_s, :to_binary
34
34
 
@@ -9,8 +9,8 @@ module Pio
9
9
  class Format < BinData::Record
10
10
  endian :big
11
11
 
12
- uint16 :type, value: 8
13
- uint16 :message_length, value: 8
12
+ uint16 :action_type, value: 8
13
+ uint16 :action_length, value: 8
14
14
  uint8 :type_of_service
15
15
  uint24 :padding
16
16
  hide :padding
@@ -24,8 +24,8 @@ module Pio
24
24
 
25
25
  extend Forwardable
26
26
 
27
- def_delegators :@format, :type
28
- def_delegators :@format, :message_length
27
+ def_delegators :@format, :action_type
28
+ def_delegator :@format, :action_length, :length
29
29
  def_delegators :@format, :type_of_service
30
30
  def_delegator :@format, :to_binary_s, :to_binary
31
31
 
@@ -6,14 +6,14 @@ module Pio
6
6
  # An action to modify the source/destination TCP/UDP port of a packet.
7
7
  class SetTransportPort
8
8
  # rubocop:disable MethodLength
9
- def self.def_format(action_type)
9
+ def self.action_type(action_type)
10
10
  str = %(
11
11
  class Format < BinData::Record
12
12
  endian :big
13
13
 
14
- uint16 :type, value: #{action_type}
15
- uint16 :message_length, value: 8
16
- uint16 :port_number
14
+ uint16 :action_type, value: #{action_type}
15
+ uint16 :action_length, value: 8
16
+ uint16 :port
17
17
  uint16 :padding
18
18
  hide :padding
19
19
  end
@@ -30,17 +30,17 @@ module Pio
30
30
 
31
31
  extend Forwardable
32
32
 
33
- def_delegators :@format, :type
34
- def_delegators :@format, :message_length
35
- def_delegators :@format, :port_number
33
+ def_delegators :@format, :action_type
34
+ def_delegator :@format, :action_length, :length
35
+ def_delegators :@format, :port
36
36
  def_delegator :@format, :to_binary_s, :to_binary
37
37
 
38
38
  def initialize(number)
39
- port_number = number.to_i
40
- unless port_number.unsigned_16bit?
39
+ port = number.to_i
40
+ unless port.unsigned_16bit?
41
41
  fail ArgumentError, 'TCP/UDP port must be an unsigned 16-bit integer.'
42
42
  end
43
- @format = self.class.const_get(:Format).new(port_number: port_number)
43
+ @format = self.class.const_get(:Format).new(port: port)
44
44
  rescue NoMethodError
45
45
  raise TypeError, 'TCP/UDP port must be an unsigned 16-bit integer.'
46
46
  end
@@ -48,11 +48,11 @@ module Pio
48
48
 
49
49
  # An action to modify the source TCP/UDP port of a packet.
50
50
  class SetTransportSourcePort < SetTransportPort
51
- def_format 9
51
+ action_type 9
52
52
  end
53
53
 
54
54
  # An action to modify the source TCP/UDP port of a packet.
55
55
  class SetTransportDestinationPort < SetTransportPort
56
- def_format 10
56
+ action_type 10
57
57
  end
58
58
  end
@@ -12,8 +12,8 @@ module Pio
12
12
  class Format < BinData::Record
13
13
  endian :big
14
14
 
15
- uint16 :type, value: #{action_type}
16
- uint16 :message_length, value: 8
15
+ uint16 :action_type, value: #{action_type}
16
+ uint16 :action_length, value: 8
17
17
  uint16 :#{field_name}
18
18
  uint16 :padding
19
19
  hide :padding
@@ -30,8 +30,8 @@ module Pio
30
30
  set_vlan
31
31
  end
32
32
 
33
- def_delegators :@format, :type
34
- def_delegators :@format, :message_length
33
+ def_delegators :@format, :action_type
34
+ def_delegator :@format, :action_length, :length
35
35
  def_delegator :@format, :to_binary_s, :to_binary
36
36
  end
37
37
  end
@@ -5,18 +5,6 @@ module Pio
5
5
  class SetVlanVid < SetVlan
6
6
  def_format :vlan_id, 1
7
7
 
8
- # Creates an action to modify the VLAN ID of a packet. The VLAN ID
9
- # is 16-bits long but the actual VID (VLAN Identifier) of the IEEE
10
- # 802.1Q frame is 12-bits.
11
- #
12
- # @example
13
- # ActionSetVlanVid.new(number)
14
- #
15
- # @param [Integer] number
16
- # the VLAN ID to set to. Only the lower 12-bits are used.
17
- #
18
- # @raise [ArgumentError] if vlan_id not within 1 and 4095 inclusive.
19
- # @raise [TypeError] if vlan_id is not an Integer.
20
8
  def initialize(number)
21
9
  vlan_id = number.to_i
22
10
  unless vlan_id >= 1 && vlan_id <= 4095
@@ -8,22 +8,22 @@ module Pio
8
8
  class Format < BinData::Record
9
9
  endian :big
10
10
 
11
- uint16 :type, value: 3
12
- uint16 :message_length, value: 8
11
+ uint16 :action_type, value: 3
12
+ uint16 :action_length, value: 8
13
13
  uint32 :padding
14
14
  hide :padding
15
15
  end
16
16
 
17
17
  def self.read(raw_data)
18
- strip_vlan = allocate
19
- strip_vlan.instance_variable_set :@format, Format.read(raw_data)
20
- strip_vlan
18
+ allocate.tap do |strip_vlan|
19
+ strip_vlan.instance_variable_set :@format, Format.read(raw_data)
20
+ end
21
21
  end
22
22
 
23
23
  extend Forwardable
24
24
 
25
- def_delegators :@format, :type
26
- def_delegators :@format, :message_length
25
+ def_delegators :@format, :action_type
26
+ def_delegator :@format, :action_length, :length
27
27
  def_delegator :@format, :to_binary_s, :to_binary
28
28
 
29
29
  def initialize
@@ -0,0 +1,33 @@
1
+ require 'bindata'
2
+ require 'forwardable'
3
+
4
+ module Pio
5
+ # Vendor defined action
6
+ class VendorAction
7
+ # OpenFlow 1.0 OFPAT_VENDOR action format.
8
+ class Format < BinData::Record
9
+ endian :big
10
+
11
+ uint16 :action_type, value: 0xffff
12
+ uint16 :action_length, value: 8
13
+ uint32 :vendor
14
+ end
15
+
16
+ def self.read(raw_data)
17
+ allocate.tap do |strip_vlan|
18
+ strip_vlan.instance_variable_set :@format, Format.read(raw_data)
19
+ end
20
+ end
21
+
22
+ extend Forwardable
23
+
24
+ def_delegators :@format, :action_type
25
+ def_delegator :@format, :action_length, :length
26
+ def_delegators :@format, :vendor
27
+ def_delegator :@format, :to_binary_s, :to_binary
28
+
29
+ def initialize(vendor)
30
+ @format = Format.new(vendor: vendor)
31
+ end
32
+ end
33
+ end
@@ -1,5 +1,8 @@
1
1
  require 'pio/open_flow13/apply'
2
2
  require 'pio/open_flow13/echo'
3
+ require 'pio/open_flow13/error'
4
+ require 'pio/open_flow13/error/bad_request'
5
+ require 'pio/open_flow13/error/hello_failed'
3
6
  require 'pio/open_flow13/features_reply'
4
7
  require 'pio/open_flow13/features_request'
5
8
  require 'pio/open_flow13/flow_mod'
@@ -17,8 +17,8 @@ module Pio
17
17
 
18
18
  string :binary, read_length: :length
19
19
 
20
- def set(value)
21
- self.binary = [value].flatten.map(&:to_binary).join
20
+ def set(actions)
21
+ self.binary = Array(actions).map(&:to_binary).join
22
22
  end
23
23
 
24
24
  # rubocop:disable MethodLength
@@ -32,7 +32,7 @@ module Pio
32
32
  else
33
33
  UnsupportedAction.read(tmp)
34
34
  end
35
- tmp = tmp[action.action_length..-1]
35
+ tmp = tmp[action.length..-1]
36
36
  actions << action
37
37
  end
38
38
  actions
@@ -0,0 +1,28 @@
1
+ require 'pio/open_flow13/error/error_type13'
2
+
3
+ module Pio
4
+ module OpenFlow13
5
+ # Error message parser
6
+ module Error
7
+ # Error message body parser.
8
+ class BodyParser < BinData::Record
9
+ endian :big
10
+ error_type13 :error_type
11
+ uint16 :error_code
12
+ end
13
+
14
+ def self.read(binary)
15
+ body = OpenFlowHeaderParser.read(binary).body
16
+ klass = case BodyParser.read(body).snapshot.error_type
17
+ when :hello_failed
18
+ HelloFailed
19
+ when :bad_request
20
+ BadRequest
21
+ else
22
+ fail 'Unknown error message.'
23
+ end
24
+ klass.read binary
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,66 @@
1
+ require 'pio/open_flow/format'
2
+ require 'pio/open_flow/message'
3
+ require 'pio/open_flow13/error/error_type13'
4
+
5
+ module Pio
6
+ module OpenFlow13
7
+ module Error
8
+ # Bad request error.
9
+ class BadRequest < OpenFlow::Message
10
+ # Bad request error format.
11
+ class Format < BinData::Record
12
+ # enum ofp_bad_request_code
13
+ class BadRequestCode < BinData::Primitive
14
+ ERROR_CODES = {
15
+ bad_version: 0,
16
+ bad_type: 1,
17
+ bad_multipart: 2,
18
+ bad_experimenter: 3,
19
+ bad_experimenter_type: 4,
20
+ permissions_error: 5,
21
+ bad_length: 6,
22
+ buffer_empty: 7,
23
+ buffer_unknown: 8,
24
+ bad_table_id: 9,
25
+ controller_is_slave: 10,
26
+ bad_port: 11,
27
+ bad_packet: 12,
28
+ multipart_buffer_overflow: 13
29
+ }
30
+
31
+ endian :big
32
+ uint16 :error_code
33
+
34
+ def get
35
+ ERROR_CODES.invert.fetch(error_code)
36
+ end
37
+
38
+ def set(value)
39
+ self.error_code = ERROR_CODES.fetch(value)
40
+ end
41
+ end
42
+
43
+ # Bad request error body.
44
+ class Body < BinData::Record
45
+ endian :big
46
+
47
+ error_type13 :error_type, value: -> { :bad_request }
48
+ bad_request_code :error_code
49
+ rest :raw_data
50
+
51
+ def length
52
+ 4 + raw_data.length
53
+ end
54
+ end
55
+
56
+ extend OpenFlow::Format
57
+
58
+ header version: 4, message_type: 1
59
+ body :body
60
+ end
61
+
62
+ body_option :raw_data
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,37 @@
1
+ module Pio
2
+ module OpenFlow13
3
+ module Error
4
+ # enum ofp_error_type
5
+ class ErrorType13 < BinData::Primitive
6
+ ERROR_TYPES = {
7
+ hello_failed: 0,
8
+ bad_request: 1,
9
+ bad_action: 2,
10
+ bad_instruction: 3,
11
+ bad_match: 4,
12
+ flow_mod_failed: 5,
13
+ group_mod_failed: 6,
14
+ port_mod_failed: 7,
15
+ table_mod_failed: 8,
16
+ queue_operation_failed: 9,
17
+ switch_config_failed: 10,
18
+ role_request_failed: 11,
19
+ meter_mod_failed: 12,
20
+ table_features_failed: 13,
21
+ experimenter: 0xffff
22
+ }
23
+
24
+ endian :big
25
+ uint16 :error_type
26
+
27
+ def get
28
+ ERROR_TYPES.invert.fetch(error_type)
29
+ end
30
+
31
+ def set(value)
32
+ self.error_type = ERROR_TYPES.fetch(value)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ require 'pio/open_flow/hello_failed_code'
2
+ require 'pio/open_flow/format'
3
+ require 'pio/open_flow/message'
4
+
5
+ # Base module.
6
+ module Pio
7
+ # OpenFlow 1.3 messages
8
+ module OpenFlow13
9
+ module Error
10
+ # Hello Failed error message
11
+ class HelloFailed < OpenFlow::Message
12
+ # Hello Failed error format.
13
+ class Format < BinData::Record
14
+ # Hello Failed error body.
15
+ class Body < BinData::Record
16
+ endian :big
17
+
18
+ error_type13 :error_type
19
+ hello_failed_code :error_code
20
+ rest :description
21
+
22
+ def length
23
+ 4 + description.length
24
+ end
25
+ end
26
+
27
+ extend OpenFlow::Format
28
+
29
+ header version: 4, message_type: 1
30
+ body :body
31
+
32
+ def length
33
+ 8 + body.length
34
+ end
35
+ end
36
+
37
+ body_option :error_code
38
+ body_option :description
39
+ end
40
+ end
41
+ end
42
+ end