pio 0.15.0 → 0.15.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/lib/pio/echo.rb +6 -8
- data/lib/pio/features.rb +7 -6
- data/lib/pio/flow_mod.rb +7 -4
- data/lib/pio/hello.rb +4 -4
- data/lib/pio/open_flow.rb +16 -1
- data/lib/pio/open_flow/flags.rb +50 -44
- data/lib/pio/open_flow/message.rb +70 -93
- data/lib/pio/open_flow/open_flow_header.rb +38 -9
- data/lib/pio/open_flow/phy_port.rb +60 -62
- data/lib/pio/packet_in.rb +8 -3
- data/lib/pio/packet_out.rb +5 -2
- data/lib/pio/port_status.rb +6 -3
- data/lib/pio/version.rb +1 -1
- data/spec/pio/echo/reply_spec.rb +6 -6
- data/spec/pio/echo/request_spec.rb +6 -6
- data/spec/pio/features/reply_spec.rb +2 -2
- data/spec/pio/features/request_spec.rb +5 -5
- data/spec/pio/open_flow/phy_port_spec.rb +7 -7
- data/spec/pio/open_flow/type_spec.rb +2 -2
- metadata +2 -3
- data/lib/pio/open_flow/type.rb +0 -16
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 72d822a526ecc76b756781275560ac85461cb743
|
|
4
|
+
data.tar.gz: 016ffba5c86e40a62e17baa73ad148796d3e1c9f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: badd7676a9c52f24e3941f47b2d6d7da1a26a14a6865119434307d08219be1c34c21336b32869b5836ef2ed89317f431c5d142d51d0d84f025297eea96cc3b77
|
|
7
|
+
data.tar.gz: 68591ec7d20cc8a5082c3742ae634e954871ec297bd7097fac3d6ebefa7af3ab439587b75b968862b973b7f396918b9d57dc0535cf1b01014627ba786a8e1071
|
data/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,11 @@
|
|
|
3
3
|
## develop (unreleased)
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
## 0.15.1 (2/17/2015)
|
|
7
|
+
### Bugs fixed
|
|
8
|
+
* [#127](https://github.com/trema/pio/pull/127): Make OpenFlow classes thread safe.
|
|
9
|
+
|
|
10
|
+
|
|
6
11
|
## 0.15.0 (2/12/2015)
|
|
7
12
|
### New features
|
|
8
13
|
* [#126](https://github.com/trema/pio/pull/126): Add new class `Pio::PortStatus`.
|
data/lib/pio/echo.rb
CHANGED
|
@@ -3,14 +3,12 @@ require 'pio/open_flow'
|
|
|
3
3
|
module Pio
|
|
4
4
|
# OpenFlow 1.0 Echo Request and Reply message.
|
|
5
5
|
module Echo
|
|
6
|
-
#
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Request < OpenFlow::Message.factory(OpenFlow::Type::ECHO_REQUEST); end
|
|
6
|
+
# OpenFlow 1.0 Echo Request message.
|
|
7
|
+
class Request; end
|
|
8
|
+
OpenFlow::Message.factory(Request, OpenFlow::ECHO_REQUEST)
|
|
10
9
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Reply < OpenFlow::Message.factory(OpenFlow::Type::ECHO_REPLY); end
|
|
10
|
+
# OpenFlow 1.0 Echo Reply message.
|
|
11
|
+
class Reply; end
|
|
12
|
+
OpenFlow::Message.factory(Reply, OpenFlow::ECHO_REPLY)
|
|
15
13
|
end
|
|
16
14
|
end
|
data/lib/pio/features.rb
CHANGED
|
@@ -4,14 +4,13 @@ module Pio
|
|
|
4
4
|
# OpenFlow 1.0 Features Request and Reply message.
|
|
5
5
|
class Features
|
|
6
6
|
# OpenFlow 1.0 Features Request message.
|
|
7
|
-
class Request
|
|
8
|
-
|
|
7
|
+
class Request; end
|
|
8
|
+
OpenFlow::Message.factory(Request, OpenFlow::FEATURES_REQUEST)
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
class Reply < OpenFlow::Message.factory(OpenFlow::Type::FEATURES_REPLY)
|
|
10
|
+
class Reply
|
|
12
11
|
# Message body of features reply.
|
|
13
|
-
class
|
|
14
|
-
extend Flags
|
|
12
|
+
class Body < BinData::Record
|
|
13
|
+
extend OpenFlow::Flags
|
|
15
14
|
|
|
16
15
|
# enum ofp_capabilities
|
|
17
16
|
flags_32bit :capabilities,
|
|
@@ -58,7 +57,9 @@ module Pio
|
|
|
58
57
|
24 + ports.to_binary_s.length
|
|
59
58
|
end
|
|
60
59
|
end
|
|
60
|
+
end
|
|
61
61
|
|
|
62
|
+
OpenFlow::Message.factory(Reply, OpenFlow::FEATURES_REPLY) do
|
|
62
63
|
def_delegators :body, :datapath_id
|
|
63
64
|
def_delegator :body, :datapath_id, :dpid
|
|
64
65
|
def_delegators :body, :n_buffers
|
data/lib/pio/flow_mod.rb
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
require 'pio/match'
|
|
2
2
|
require 'pio/open_flow'
|
|
3
3
|
|
|
4
|
+
# Base module.
|
|
4
5
|
module Pio
|
|
5
|
-
# OpenFlow 1.0
|
|
6
|
-
class FlowMod
|
|
6
|
+
# OpenFlow 1.0 Flow Mod message.
|
|
7
|
+
class FlowMod
|
|
7
8
|
# enum ofp_flow_mod_command
|
|
8
9
|
class Command < BinData::Primitive
|
|
9
10
|
COMMANDS = {
|
|
@@ -27,7 +28,7 @@ module Pio
|
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
# Message body of FlowMod.
|
|
30
|
-
class
|
|
31
|
+
class Body < BinData::Record
|
|
31
32
|
# Pio::MatchFormat wrapper.
|
|
32
33
|
class Match < BinData::Primitive
|
|
33
34
|
endian :big
|
|
@@ -43,7 +44,7 @@ module Pio
|
|
|
43
44
|
end
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
extend Flags
|
|
47
|
+
extend OpenFlow::Flags
|
|
47
48
|
|
|
48
49
|
flags_16bit :flags,
|
|
49
50
|
[:send_flow_rem,
|
|
@@ -71,7 +72,9 @@ module Pio
|
|
|
71
72
|
64 + actions.binary.length
|
|
72
73
|
end
|
|
73
74
|
end
|
|
75
|
+
end
|
|
74
76
|
|
|
77
|
+
OpenFlow::Message.factory(FlowMod, OpenFlow::FLOW_MOD) do
|
|
75
78
|
def_delegators :body, :match
|
|
76
79
|
def_delegators :body, :cookie
|
|
77
80
|
def_delegators :body, :command
|
data/lib/pio/hello.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
require 'pio/open_flow'
|
|
2
2
|
|
|
3
|
+
# Base module.
|
|
3
4
|
module Pio
|
|
4
|
-
#
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Hello < OpenFlow::Message.factory(Pio::OpenFlow::Type::HELLO); end
|
|
5
|
+
# OpenFlow 1.0 Hello message
|
|
6
|
+
class Hello; end
|
|
7
|
+
OpenFlow::Message.factory(Hello, OpenFlow::HELLO)
|
|
8
8
|
end
|
data/lib/pio/open_flow.rb
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
+
module Pio
|
|
2
|
+
# OpenFlow specific types.
|
|
3
|
+
module OpenFlow
|
|
4
|
+
# OFPT_* constants.
|
|
5
|
+
HELLO = 0
|
|
6
|
+
ECHO_REQUEST = 2
|
|
7
|
+
ECHO_REPLY = 3
|
|
8
|
+
FEATURES_REQUEST = 5
|
|
9
|
+
FEATURES_REPLY = 6
|
|
10
|
+
PACKET_IN = 10
|
|
11
|
+
PORT_STATUS = 12
|
|
12
|
+
PACKET_OUT = 13
|
|
13
|
+
FLOW_MOD = 14
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
1
17
|
require 'pio/open_flow/actions'
|
|
2
18
|
require 'pio/open_flow/flags'
|
|
3
19
|
require 'pio/open_flow/message'
|
|
4
20
|
require 'pio/open_flow/open_flow_header'
|
|
5
21
|
require 'pio/open_flow/phy_port'
|
|
6
22
|
require 'pio/open_flow/port_number'
|
|
7
|
-
require 'pio/open_flow/type'
|
data/lib/pio/open_flow/flags.rb
CHANGED
|
@@ -1,55 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
module Pio
|
|
2
|
+
module OpenFlow
|
|
3
|
+
# bitmap functions.
|
|
4
|
+
# This class smells of :reek:DataClump
|
|
5
|
+
module Flags
|
|
6
|
+
def flags_32bit(name, flags)
|
|
7
|
+
_def_flags name, 32, flags
|
|
8
|
+
end
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
def flags_16bit(name, flags)
|
|
11
|
+
_def_flags name, 16, flags
|
|
12
|
+
end
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
# rubocop:disable MethodLength
|
|
15
|
+
# This method smells of :reek:TooManyStatements
|
|
16
|
+
def _def_flags(name, size, flags)
|
|
17
|
+
flag_value = case flags
|
|
18
|
+
when Array
|
|
19
|
+
shift = 0
|
|
20
|
+
flags.each_with_object({}) do |each, result|
|
|
21
|
+
result[each] = 1 << shift
|
|
22
|
+
shift += 1
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
when Hash
|
|
26
|
+
flags
|
|
27
|
+
end
|
|
26
28
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
klass_name = name.to_s.split('_').map(&:capitalize).join
|
|
30
|
+
flags_hash = flag_value.inspect
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
code = %{
|
|
33
|
+
class #{klass_name} < BinData::Primitive
|
|
34
|
+
endian :big
|
|
33
35
|
|
|
34
|
-
|
|
36
|
+
uint#{size} :#{name}
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
def get
|
|
39
|
+
list = #{flags_hash}
|
|
40
|
+
list.each_with_object([]) do |(key, value), result|
|
|
41
|
+
result << key if #{name} & value != 0
|
|
42
|
+
result
|
|
43
|
+
end
|
|
44
|
+
end
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
def set(v)
|
|
47
|
+
list = #{flags_hash}
|
|
48
|
+
v.each do |each|
|
|
49
|
+
fail "Invalid state flag: \#{v}" unless list.keys.include?(each)
|
|
50
|
+
end
|
|
51
|
+
self.#{name} = v.empty? ?
|
|
52
|
+
0 :
|
|
53
|
+
v.map { |each| list[each] }.inject(:|)
|
|
54
|
+
end
|
|
48
55
|
end
|
|
49
|
-
|
|
50
|
-
|
|
56
|
+
}
|
|
57
|
+
module_eval code
|
|
51
58
|
end
|
|
52
|
-
|
|
53
|
-
module_eval code
|
|
59
|
+
end
|
|
54
60
|
end
|
|
55
61
|
end
|
|
@@ -7,126 +7,103 @@ module Pio
|
|
|
7
7
|
module OpenFlow
|
|
8
8
|
# Defines shortcuts to OpenFlow header fields.
|
|
9
9
|
class Message
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def_delegator :open_flow_header, :transaction_id, :xid
|
|
19
|
-
|
|
20
|
-
def_delegators :@format, :body
|
|
21
|
-
def_delegator :@format, :body, :user_data
|
|
22
|
-
|
|
23
|
-
def_delegator :@format, :to_binary_s, :to_binary
|
|
24
|
-
|
|
25
|
-
def self.factory(message_type)
|
|
26
|
-
@message_type = message_type
|
|
27
|
-
self
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def self.klass(message_type)
|
|
31
|
-
@messages.fetch(message_type)
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def self.inherited(child)
|
|
35
|
-
child.const_set(:MESSAGE_TYPE, @message_type)
|
|
36
|
-
@messages ||= {}
|
|
37
|
-
@messages[@message_type] = child
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def self.read(raw_data)
|
|
41
|
-
message = allocate
|
|
42
|
-
message.instance_variable_set(:@format, format.read(raw_data))
|
|
43
|
-
message
|
|
44
|
-
rescue BinData::ValidityError
|
|
45
|
-
message_name = name.split('::')[1..-1].join(' ')
|
|
46
|
-
raise Pio::ParseError, "Invalid #{message_name} message."
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def self.format
|
|
50
|
-
const_get(:Format)
|
|
51
|
-
rescue NameError
|
|
52
|
-
define_message_format const_get(:MESSAGE_TYPE)
|
|
53
|
-
retry
|
|
10
|
+
def self.factory(klass, message_type, &block)
|
|
11
|
+
klass.extend Forwardable
|
|
12
|
+
klass.module_eval(&block) if block
|
|
13
|
+
klass.module_eval _format_class(klass, message_type)
|
|
14
|
+
klass.module_eval(&_define_open_flow_accessors)
|
|
15
|
+
klass.module_eval(&_define_self_read)
|
|
16
|
+
klass.module_eval(&_define_initialize)
|
|
17
|
+
klass.module_eval(&_define_to_binary)
|
|
54
18
|
end
|
|
55
19
|
|
|
56
20
|
# rubocop:disable MethodLength
|
|
57
|
-
def self.
|
|
58
|
-
|
|
21
|
+
def self._format_class(klass, message_type)
|
|
22
|
+
%(
|
|
59
23
|
class Format < BinData::Record
|
|
60
24
|
endian :big
|
|
61
25
|
|
|
62
26
|
open_flow_header :open_flow_header,
|
|
63
|
-
|
|
27
|
+
message_type_value: #{message_type}
|
|
64
28
|
virtual assert: -> do
|
|
65
29
|
open_flow_header.message_type == #{message_type}
|
|
66
30
|
end
|
|
67
31
|
|
|
68
|
-
#{
|
|
32
|
+
#{klass.const_defined?(:Body) ? 'body' : 'string'} :body
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.format
|
|
36
|
+
const_get :Format
|
|
69
37
|
end
|
|
70
38
|
)
|
|
71
|
-
module_eval code
|
|
72
39
|
end
|
|
73
40
|
# rubocop:enable MethodLength
|
|
74
41
|
|
|
75
|
-
def self.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
42
|
+
def self._define_open_flow_accessors
|
|
43
|
+
proc do
|
|
44
|
+
def_delegators :@format, :open_flow_header
|
|
45
|
+
def_delegators :open_flow_header, :ofp_version
|
|
46
|
+
def_delegators :open_flow_header, :message_type
|
|
47
|
+
def_delegators :open_flow_header, :message_length
|
|
48
|
+
def_delegators :open_flow_header, :transaction_id
|
|
49
|
+
def_delegator :open_flow_header, :transaction_id, :xid
|
|
50
|
+
|
|
51
|
+
def_delegators :@format, :body
|
|
52
|
+
def_delegator :@format, :body, :user_data
|
|
53
|
+
end
|
|
85
54
|
end
|
|
86
55
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
56
|
+
def self._define_self_read
|
|
57
|
+
proc do
|
|
58
|
+
def self.read(raw_data)
|
|
59
|
+
allocate.tap do |message|
|
|
60
|
+
message.instance_variable_set(:@format, format.read(raw_data))
|
|
61
|
+
end
|
|
62
|
+
rescue BinData::ValidityError
|
|
63
|
+
message_name = name.split('::')[1..-1].join(' ')
|
|
64
|
+
raise Pio::ParseError, "Invalid #{message_name} message."
|
|
65
|
+
end
|
|
66
|
+
end
|
|
92
67
|
end
|
|
93
68
|
|
|
94
|
-
|
|
69
|
+
# rubocop:disable MethodLength
|
|
70
|
+
# rubocop:disable AbcSize
|
|
71
|
+
def self._define_initialize
|
|
72
|
+
proc do
|
|
73
|
+
def initialize(user_options = {})
|
|
74
|
+
header_options = OpenFlowHeader::Options.parse(user_options)
|
|
75
|
+
body_options = parse_body_options(user_options)
|
|
76
|
+
@format = self.class.format.new(open_flow_header: header_options,
|
|
77
|
+
body: body_options)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
95
81
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
82
|
+
def parse_body_options(options)
|
|
83
|
+
if options.respond_to?(:fetch)
|
|
84
|
+
options.delete :transaction_id
|
|
85
|
+
options.delete :xid
|
|
86
|
+
dpid = options[:dpid]
|
|
87
|
+
options[:datapath_id] = dpid if dpid
|
|
88
|
+
if options.keys.size > 1
|
|
89
|
+
options
|
|
102
90
|
else
|
|
103
|
-
|
|
91
|
+
options[:user_data] || ''
|
|
104
92
|
end
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# rubocop:disable MethodLength
|
|
111
|
-
# This method smells of :reek:TooManyStatements
|
|
112
|
-
# This method smells of :reek:FeatureEnvy
|
|
113
|
-
# This method smells of :reek:UtilityFunction
|
|
114
|
-
def parse_body_options(options)
|
|
115
|
-
if options.respond_to?(:fetch)
|
|
116
|
-
options.delete :transaction_id
|
|
117
|
-
options.delete :xid
|
|
118
|
-
dpid = options[:dpid]
|
|
119
|
-
options[:datapath_id] = dpid if dpid
|
|
120
|
-
if options.keys.size > 1
|
|
121
|
-
options
|
|
122
|
-
else
|
|
123
|
-
options[:user_data] || ''
|
|
93
|
+
else
|
|
94
|
+
''
|
|
95
|
+
end
|
|
124
96
|
end
|
|
125
|
-
else
|
|
126
|
-
''
|
|
127
97
|
end
|
|
128
98
|
end
|
|
129
99
|
# rubocop:enable MethodLength
|
|
100
|
+
# rubocop:enable AbcSize
|
|
101
|
+
|
|
102
|
+
def self._define_to_binary
|
|
103
|
+
proc do
|
|
104
|
+
def_delegator :@format, :to_binary_s, :to_binary
|
|
105
|
+
end
|
|
106
|
+
end
|
|
130
107
|
end
|
|
131
108
|
end
|
|
132
109
|
end
|
|
@@ -1,17 +1,46 @@
|
|
|
1
1
|
require 'bindata'
|
|
2
2
|
|
|
3
3
|
module Pio
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
# OpenFlow 1.0 format.
|
|
5
|
+
module OpenFlow
|
|
6
|
+
# OpenFlow 1.0 message header format.
|
|
7
|
+
class OpenFlowHeader < BinData::Record
|
|
8
|
+
# Transaction ID (uint32)
|
|
9
|
+
class TransactionId < BinData::Primitive
|
|
9
10
|
endian :big
|
|
11
|
+
uint32 :xid
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
def set(value)
|
|
14
|
+
unless value.unsigned_32bit?
|
|
15
|
+
fail(ArgumentError,
|
|
16
|
+
'Transaction ID should be an unsigned 32-bit integer.')
|
|
17
|
+
end
|
|
18
|
+
self.xid = value
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def get
|
|
22
|
+
xid
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
endian :big
|
|
27
|
+
uint8 :ofp_version, value: 1
|
|
28
|
+
uint8 :message_type, initial_value: :message_type_value
|
|
29
|
+
uint16 :message_length, initial_value: -> { 8 + body.length }
|
|
30
|
+
transaction_id :transaction_id, initial_value: 0
|
|
31
|
+
|
|
32
|
+
# parse header options
|
|
33
|
+
class Options
|
|
34
|
+
def self.parse(options)
|
|
35
|
+
xid = if options.respond_to?(:to_i)
|
|
36
|
+
options.to_i
|
|
37
|
+
elsif options.respond_to?(:fetch)
|
|
38
|
+
options[:transaction_id] || options[:xid] || 0
|
|
39
|
+
else
|
|
40
|
+
fail TypeError
|
|
41
|
+
end
|
|
42
|
+
{ transaction_id: xid }
|
|
43
|
+
end
|
|
15
44
|
end
|
|
16
45
|
end
|
|
17
46
|
end
|
|
@@ -2,79 +2,77 @@ require 'bindata'
|
|
|
2
2
|
require 'pio/type/mac_address'
|
|
3
3
|
|
|
4
4
|
module Pio
|
|
5
|
-
module
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
extend Flags
|
|
5
|
+
module OpenFlow
|
|
6
|
+
# Description of a physical port
|
|
7
|
+
class PhyPort < BinData::Record
|
|
8
|
+
extend Flags
|
|
10
9
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
# enum ofp_port_config
|
|
11
|
+
flags_32bit :port_config,
|
|
12
|
+
[:port_down,
|
|
13
|
+
:no_stp,
|
|
14
|
+
:no_recv,
|
|
15
|
+
:no_recv_stp,
|
|
16
|
+
:no_flood,
|
|
17
|
+
:no_fwd,
|
|
18
|
+
:no_packet_in]
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
20
|
+
# enum ofp_port_state
|
|
21
|
+
flags_32bit :port_state,
|
|
22
|
+
link_down: 1 << 0,
|
|
23
|
+
stp_listen: 0 << 8,
|
|
24
|
+
stp_learn: 1 << 8,
|
|
25
|
+
stp_forward: 2 << 8,
|
|
26
|
+
stp_block: 3 << 8
|
|
28
27
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
# enum ofp_port_features
|
|
29
|
+
flags_32bit :port_feature,
|
|
30
|
+
[:port_10mb_hd,
|
|
31
|
+
:port_10mb_fd,
|
|
32
|
+
:port_100mb_hd,
|
|
33
|
+
:port_100mb_fd,
|
|
34
|
+
:port_1gb_hd,
|
|
35
|
+
:port_1gb_fd,
|
|
36
|
+
:port_10gb_fd,
|
|
37
|
+
:port_copper,
|
|
38
|
+
:port_fiber,
|
|
39
|
+
:port_autoneg,
|
|
40
|
+
:port_pause,
|
|
41
|
+
:port_pause_asym]
|
|
43
42
|
|
|
44
|
-
|
|
43
|
+
endian :big
|
|
45
44
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
45
|
+
uint16 :port_no
|
|
46
|
+
mac_address :hardware_address
|
|
47
|
+
string :name, length: 16, trim_padding: true
|
|
48
|
+
port_config :config
|
|
49
|
+
port_state :state
|
|
50
|
+
port_feature :curr
|
|
51
|
+
port_feature :advertised
|
|
52
|
+
port_feature :supported
|
|
53
|
+
port_feature :peer
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
attr_accessor :datapath_id
|
|
56
|
+
alias_method :dpid, :datapath_id
|
|
58
57
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
def number
|
|
59
|
+
port_no
|
|
60
|
+
end
|
|
62
61
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
def mac_address
|
|
63
|
+
hardware_address
|
|
64
|
+
end
|
|
66
65
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
def up?
|
|
67
|
+
!down?
|
|
68
|
+
end
|
|
70
69
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
def down?
|
|
71
|
+
config.include?(:port_down) || state.include?(:link_down)
|
|
72
|
+
end
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end
|
|
74
|
+
def local?
|
|
75
|
+
port_no == PortNumber::NUMBERS[:local]
|
|
78
76
|
end
|
|
79
77
|
end
|
|
80
78
|
end
|
data/lib/pio/packet_in.rb
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
require 'bindata'
|
|
2
2
|
require 'pio/open_flow'
|
|
3
3
|
require 'pio/parse_error'
|
|
4
|
+
require 'pio/type/ethernet_header'
|
|
5
|
+
require 'pio/type/ipv4_header'
|
|
4
6
|
|
|
7
|
+
# Base module.
|
|
5
8
|
module Pio
|
|
6
9
|
# OpenFlow 1.0 Packet-In message
|
|
7
|
-
class PacketIn
|
|
10
|
+
class PacketIn
|
|
8
11
|
# Why is this packet being sent to the controller?
|
|
9
12
|
# (enum ofp_packet_in_reason)
|
|
10
13
|
class Reason < BinData::Primitive
|
|
@@ -22,7 +25,7 @@ module Pio
|
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
# Message body of Packet-In.
|
|
25
|
-
class
|
|
28
|
+
class Body < BinData::Record
|
|
26
29
|
endian :big
|
|
27
30
|
|
|
28
31
|
uint32 :buffer_id
|
|
@@ -85,7 +88,9 @@ module Pio
|
|
|
85
88
|
end
|
|
86
89
|
# rubocop:enable MethodLength
|
|
87
90
|
end
|
|
91
|
+
end
|
|
88
92
|
|
|
93
|
+
OpenFlow::Message.factory(PacketIn, OpenFlow::PACKET_IN) do
|
|
89
94
|
attr_accessor :datapath_id
|
|
90
95
|
alias_method :dpid, :datapath_id
|
|
91
96
|
alias_method :dpid=, :datapath_id=
|
|
@@ -97,7 +102,7 @@ module Pio
|
|
|
97
102
|
def_delegators :body, :data
|
|
98
103
|
|
|
99
104
|
def parsed_data
|
|
100
|
-
@parsed_data ||= DataParser.read(data)
|
|
105
|
+
@parsed_data ||= PacketIn::DataParser.read(data)
|
|
101
106
|
end
|
|
102
107
|
|
|
103
108
|
def lldp?
|
data/lib/pio/packet_out.rb
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
require 'bindata'
|
|
2
2
|
require 'pio/open_flow'
|
|
3
3
|
|
|
4
|
+
# Base module.
|
|
4
5
|
module Pio
|
|
5
6
|
# OpenFlow 1.0 Packet-Out message
|
|
6
|
-
class PacketOut
|
|
7
|
+
class PacketOut
|
|
7
8
|
# Message body of Packet-Out
|
|
8
|
-
class
|
|
9
|
+
class Body < BinData::Record
|
|
9
10
|
endian :big
|
|
10
11
|
|
|
11
12
|
uint32 :buffer_id
|
|
@@ -22,7 +23,9 @@ module Pio
|
|
|
22
23
|
8 + actions_len + data.length
|
|
23
24
|
end
|
|
24
25
|
end
|
|
26
|
+
end
|
|
25
27
|
|
|
28
|
+
OpenFlow::Message.factory(PacketOut, OpenFlow::PACKET_OUT) do
|
|
26
29
|
def_delegators :body, :buffer_id
|
|
27
30
|
def_delegators :body, :in_port
|
|
28
31
|
def_delegators :body, :actions_len
|
data/lib/pio/port_status.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
require 'pio/open_flow'
|
|
2
2
|
|
|
3
|
+
# Base module.
|
|
3
4
|
module Pio
|
|
4
5
|
# OpenFlow 1.0 Port Status message
|
|
5
|
-
class PortStatus
|
|
6
|
+
class PortStatus
|
|
6
7
|
# What changed about the physical port
|
|
7
8
|
class Reason < BinData::Primitive
|
|
8
9
|
REASONS = { add: 0, delete: 1, modify: 2 }
|
|
@@ -18,8 +19,8 @@ module Pio
|
|
|
18
19
|
end
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
# Message body of
|
|
22
|
-
class
|
|
22
|
+
# Message body of Port Status
|
|
23
|
+
class Body < BinData::Record
|
|
23
24
|
endian :big
|
|
24
25
|
|
|
25
26
|
reason :reason
|
|
@@ -27,7 +28,9 @@ module Pio
|
|
|
27
28
|
hide :padding
|
|
28
29
|
phy_port :desc
|
|
29
30
|
end
|
|
31
|
+
end
|
|
30
32
|
|
|
33
|
+
OpenFlow::Message.factory(PortStatus, OpenFlow::PORT_STATUS) do
|
|
31
34
|
def_delegators :body, :desc
|
|
32
35
|
|
|
33
36
|
def reason
|
data/lib/pio/version.rb
CHANGED
data/spec/pio/echo/reply_spec.rb
CHANGED
|
@@ -8,7 +8,7 @@ describe Pio::Echo::Reply do
|
|
|
8
8
|
Given(:binary) { [1, 3, 0, 8, 0, 0, 0, 0].pack('C*') }
|
|
9
9
|
|
|
10
10
|
Then { echo_reply.ofp_version == 1 }
|
|
11
|
-
Then { echo_reply.message_type == Pio::OpenFlow::
|
|
11
|
+
Then { echo_reply.message_type == Pio::OpenFlow::ECHO_REPLY }
|
|
12
12
|
Then { echo_reply.message_length == 8 }
|
|
13
13
|
Then { echo_reply.transaction_id == 0 }
|
|
14
14
|
Then { echo_reply.xid == 0 }
|
|
@@ -34,7 +34,7 @@ describe Pio::Echo::Reply do
|
|
|
34
34
|
When(:echo_reply) { Pio::Echo::Reply.new }
|
|
35
35
|
|
|
36
36
|
Then { echo_reply.ofp_version == 1 }
|
|
37
|
-
Then { echo_reply.message_type == Pio::OpenFlow::
|
|
37
|
+
Then { echo_reply.message_type == Pio::OpenFlow::ECHO_REPLY }
|
|
38
38
|
Then { echo_reply.message_length == 8 }
|
|
39
39
|
Then { echo_reply.transaction_id == 0 }
|
|
40
40
|
Then { echo_reply.xid == 0 }
|
|
@@ -49,7 +49,7 @@ describe Pio::Echo::Reply do
|
|
|
49
49
|
When(:echo_reply) { Pio::Echo::Reply.new(123) }
|
|
50
50
|
|
|
51
51
|
Then { echo_reply.ofp_version == 1 }
|
|
52
|
-
Then { echo_reply.message_type == Pio::OpenFlow::
|
|
52
|
+
Then { echo_reply.message_type == Pio::OpenFlow::ECHO_REPLY }
|
|
53
53
|
Then { echo_reply.message_length == 8 }
|
|
54
54
|
Then { echo_reply.transaction_id == 123 }
|
|
55
55
|
Then { echo_reply.xid == 123 }
|
|
@@ -64,7 +64,7 @@ describe Pio::Echo::Reply do
|
|
|
64
64
|
When(:echo_reply) { Pio::Echo::Reply.new(transaction_id: 123) }
|
|
65
65
|
|
|
66
66
|
Then { echo_reply.ofp_version == 1 }
|
|
67
|
-
Then { echo_reply.message_type == Pio::OpenFlow::
|
|
67
|
+
Then { echo_reply.message_type == Pio::OpenFlow::ECHO_REPLY }
|
|
68
68
|
Then { echo_reply.message_length == 8 }
|
|
69
69
|
Then { echo_reply.transaction_id == 123 }
|
|
70
70
|
Then { echo_reply.xid == 123 }
|
|
@@ -79,7 +79,7 @@ describe Pio::Echo::Reply do
|
|
|
79
79
|
When(:echo_reply) { Pio::Echo::Reply.new(xid: 123) }
|
|
80
80
|
|
|
81
81
|
Then { echo_reply.ofp_version == 1 }
|
|
82
|
-
Then { echo_reply.message_type == Pio::OpenFlow::
|
|
82
|
+
Then { echo_reply.message_type == Pio::OpenFlow::ECHO_REPLY }
|
|
83
83
|
Then { echo_reply.message_length == 8 }
|
|
84
84
|
Then { echo_reply.transaction_id == 123 }
|
|
85
85
|
Then { echo_reply.xid == 123 }
|
|
@@ -94,7 +94,7 @@ describe Pio::Echo::Reply do
|
|
|
94
94
|
When(:echo_reply) { Pio::Echo::Reply.new(xid: 123, user_data: 'foobar') }
|
|
95
95
|
|
|
96
96
|
Then { echo_reply.ofp_version == 1 }
|
|
97
|
-
Then { echo_reply.message_type == Pio::OpenFlow::
|
|
97
|
+
Then { echo_reply.message_type == Pio::OpenFlow::ECHO_REPLY }
|
|
98
98
|
Then { echo_reply.message_length == 14 }
|
|
99
99
|
Then { echo_reply.transaction_id == 123 }
|
|
100
100
|
Then { echo_reply.xid == 123 }
|
|
@@ -8,7 +8,7 @@ describe Pio::Echo::Request do
|
|
|
8
8
|
Given(:binary) { [1, 2, 0, 8, 0, 0, 0, 0].pack('C*') }
|
|
9
9
|
|
|
10
10
|
Then { echo_request.ofp_version == 1 }
|
|
11
|
-
Then { echo_request.message_type == Pio::OpenFlow::
|
|
11
|
+
Then { echo_request.message_type == Pio::OpenFlow::ECHO_REQUEST }
|
|
12
12
|
Then { echo_request.message_length == 8 }
|
|
13
13
|
Then { echo_request.transaction_id == 0 }
|
|
14
14
|
Then { echo_request.xid == 0 }
|
|
@@ -34,7 +34,7 @@ describe Pio::Echo::Request do
|
|
|
34
34
|
When(:echo_request) { Pio::Echo::Request.new }
|
|
35
35
|
|
|
36
36
|
Then { echo_request.ofp_version == 1 }
|
|
37
|
-
Then { echo_request.message_type == Pio::OpenFlow::
|
|
37
|
+
Then { echo_request.message_type == Pio::OpenFlow::ECHO_REQUEST }
|
|
38
38
|
Then { echo_request.message_length == 8 }
|
|
39
39
|
Then { echo_request.transaction_id == 0 }
|
|
40
40
|
Then { echo_request.xid == 0 }
|
|
@@ -49,7 +49,7 @@ describe Pio::Echo::Request do
|
|
|
49
49
|
When(:echo_request) { Pio::Echo::Request.new(123) }
|
|
50
50
|
|
|
51
51
|
Then { echo_request.ofp_version == 1 }
|
|
52
|
-
Then { echo_request.message_type == Pio::OpenFlow::
|
|
52
|
+
Then { echo_request.message_type == Pio::OpenFlow::ECHO_REQUEST }
|
|
53
53
|
Then { echo_request.message_length == 8 }
|
|
54
54
|
Then { echo_request.transaction_id == 123 }
|
|
55
55
|
Then { echo_request.xid == 123 }
|
|
@@ -64,7 +64,7 @@ describe Pio::Echo::Request do
|
|
|
64
64
|
When(:echo_request) { Pio::Echo::Request.new(transaction_id: 123) }
|
|
65
65
|
|
|
66
66
|
Then { echo_request.ofp_version == 1 }
|
|
67
|
-
Then { echo_request.message_type == Pio::OpenFlow::
|
|
67
|
+
Then { echo_request.message_type == Pio::OpenFlow::ECHO_REQUEST }
|
|
68
68
|
Then { echo_request.message_length == 8 }
|
|
69
69
|
Then { echo_request.transaction_id == 123 }
|
|
70
70
|
Then { echo_request.xid == 123 }
|
|
@@ -79,7 +79,7 @@ describe Pio::Echo::Request do
|
|
|
79
79
|
When(:echo_request) { Pio::Echo::Request.new(xid: 123) }
|
|
80
80
|
|
|
81
81
|
Then { echo_request.ofp_version == 1 }
|
|
82
|
-
Then { echo_request.message_type == Pio::OpenFlow::
|
|
82
|
+
Then { echo_request.message_type == Pio::OpenFlow::ECHO_REQUEST }
|
|
83
83
|
Then { echo_request.message_length == 8 }
|
|
84
84
|
Then { echo_request.transaction_id == 123 }
|
|
85
85
|
Then { echo_request.xid == 123 }
|
|
@@ -96,7 +96,7 @@ describe Pio::Echo::Request do
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
Then { echo_request.ofp_version == 1 }
|
|
99
|
-
Then { echo_request.message_type == Pio::OpenFlow::
|
|
99
|
+
Then { echo_request.message_type == Pio::OpenFlow::ECHO_REQUEST }
|
|
100
100
|
Then { echo_request.message_length == 14 }
|
|
101
101
|
Then { echo_request.transaction_id == 123 }
|
|
102
102
|
Then { echo_request.xid == 123 }
|
|
@@ -30,7 +30,7 @@ describe Pio::Features::Reply do
|
|
|
30
30
|
Then { result.ofp_version == 1 }
|
|
31
31
|
Then do
|
|
32
32
|
result.message_type ==
|
|
33
|
-
Pio::OpenFlow::
|
|
33
|
+
Pio::OpenFlow::FEATURES_REPLY
|
|
34
34
|
end
|
|
35
35
|
Then { result.message_length == 176 }
|
|
36
36
|
Then { result.transaction_id == 2 }
|
|
@@ -101,7 +101,7 @@ describe Pio::Features::Reply do
|
|
|
101
101
|
When(:features_reply) { Pio::Features::Reply.new(options) }
|
|
102
102
|
|
|
103
103
|
Then { features_reply.ofp_version == 1 }
|
|
104
|
-
Then { features_reply.message_type == Pio::OpenFlow::
|
|
104
|
+
Then { features_reply.message_type == Pio::OpenFlow::FEATURES_REPLY }
|
|
105
105
|
Then { features_reply.transaction_id == 0 }
|
|
106
106
|
Then { features_reply.xid == 0 }
|
|
107
107
|
Then { features_reply.dpid == 0x123 }
|
|
@@ -11,7 +11,7 @@ describe Pio::Features::Request do
|
|
|
11
11
|
Then { result.ofp_version == 1 }
|
|
12
12
|
Then do
|
|
13
13
|
result.message_type ==
|
|
14
|
-
Pio::OpenFlow::
|
|
14
|
+
Pio::OpenFlow::FEATURES_REQUEST
|
|
15
15
|
end
|
|
16
16
|
Then { result.message_length == 8 }
|
|
17
17
|
Then { result.transaction_id == 0 }
|
|
@@ -35,7 +35,7 @@ describe Pio::Features::Request do
|
|
|
35
35
|
When(:result) { Pio::Features::Request.new }
|
|
36
36
|
|
|
37
37
|
Then { result.ofp_version == 1 }
|
|
38
|
-
Then { result.message_type == Pio::OpenFlow::
|
|
38
|
+
Then { result.message_type == Pio::OpenFlow::FEATURES_REQUEST }
|
|
39
39
|
Then { result.message_length == 8 }
|
|
40
40
|
Then { result.transaction_id == 0 }
|
|
41
41
|
Then { result.xid == 0 }
|
|
@@ -48,7 +48,7 @@ describe Pio::Features::Request do
|
|
|
48
48
|
When(:result) { Pio::Features::Request.new(123) }
|
|
49
49
|
|
|
50
50
|
Then { result.ofp_version == 1 }
|
|
51
|
-
Then { result.message_type == Pio::OpenFlow::
|
|
51
|
+
Then { result.message_type == Pio::OpenFlow::FEATURES_REQUEST }
|
|
52
52
|
Then { result.message_length == 8 }
|
|
53
53
|
Then { result.transaction_id == 123 }
|
|
54
54
|
Then { result.xid == 123 }
|
|
@@ -61,7 +61,7 @@ describe Pio::Features::Request do
|
|
|
61
61
|
When(:result) { Pio::Features::Request.new(transaction_id: 123) }
|
|
62
62
|
|
|
63
63
|
Then { result.ofp_version == 1 }
|
|
64
|
-
Then { result.message_type == Pio::OpenFlow::
|
|
64
|
+
Then { result.message_type == Pio::OpenFlow::FEATURES_REQUEST }
|
|
65
65
|
Then { result.message_length == 8 }
|
|
66
66
|
Then { result.transaction_id == 123 }
|
|
67
67
|
Then { result.xid == 123 }
|
|
@@ -74,7 +74,7 @@ describe Pio::Features::Request do
|
|
|
74
74
|
When(:result) { Pio::Features::Request.new(xid: 123) }
|
|
75
75
|
|
|
76
76
|
Then { result.ofp_version == 1 }
|
|
77
|
-
Then { result.message_type == Pio::OpenFlow::
|
|
77
|
+
Then { result.message_type == Pio::OpenFlow::FEATURES_REQUEST }
|
|
78
78
|
Then { result.message_length == 8 }
|
|
79
79
|
Then { result.transaction_id == 123 }
|
|
80
80
|
Then { result.xid == 123 }
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
require 'pio'
|
|
2
2
|
|
|
3
|
-
describe Pio::
|
|
3
|
+
describe Pio::OpenFlow::PhyPort do
|
|
4
4
|
describe '.new' do
|
|
5
5
|
When(:phy_port) do
|
|
6
|
-
Pio::
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
Pio::OpenFlow::PhyPort.new(port_no: 1,
|
|
7
|
+
hardware_address: '11:22:33:44:55:66',
|
|
8
|
+
name: 'port123',
|
|
9
|
+
config: [:port_down],
|
|
10
|
+
state: [:link_down],
|
|
11
|
+
curr: [:port_10gb_fd, :port_copper])
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
Then { phy_port.port_no == 1 }
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pio
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.15.
|
|
4
|
+
version: 0.15.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yasuhito Takamiya
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2015-02-
|
|
11
|
+
date: 2015-02-17 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bindata
|
|
@@ -503,7 +503,6 @@ files:
|
|
|
503
503
|
- lib/pio/open_flow/open_flow_header.rb
|
|
504
504
|
- lib/pio/open_flow/phy_port.rb
|
|
505
505
|
- lib/pio/open_flow/port_number.rb
|
|
506
|
-
- lib/pio/open_flow/type.rb
|
|
507
506
|
- lib/pio/options.rb
|
|
508
507
|
- lib/pio/packet_in.rb
|
|
509
508
|
- lib/pio/packet_out.rb
|
data/lib/pio/open_flow/type.rb
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module Pio
|
|
2
|
-
module OpenFlow
|
|
3
|
-
# OFPT_* constants.
|
|
4
|
-
module Type
|
|
5
|
-
HELLO = 0
|
|
6
|
-
ECHO_REQUEST = 2
|
|
7
|
-
ECHO_REPLY = 3
|
|
8
|
-
FEATURES_REQUEST = 5
|
|
9
|
-
FEATURES_REPLY = 6
|
|
10
|
-
PACKET_IN = 10
|
|
11
|
-
PORT_STATUS = 12
|
|
12
|
-
PACKET_OUT = 13
|
|
13
|
-
FLOW_MOD = 14
|
|
14
|
-
end
|
|
15
|
-
end
|
|
16
|
-
end
|