diff --git a/.gitignore b/.gitignore index e003a5b1..911dd90e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ *.rbc /.config /.idea/ -/Gemfile.lock /InstalledFiles /bin/ /coverage/ diff --git a/.rubocop.yml b/.rubocop.yml index 7a641972..0a14b786 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -12,8 +12,9 @@ AllCops: Metrics/LineLength: Exclude: + - 'fixtures/**/*.rb' - 'spec/support/*.rb' - + Style/StringLiterals: EnforcedStyle: single_quotes diff --git a/.travis.yml b/.travis.yml index 96fcdd0a..3ed51302 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,8 @@ language: ruby -before_install: gem update bundler +before_install: + - gem update bundler + bundler_args: --without development env: @@ -9,15 +11,15 @@ env: - secure: cbR8GBZyaDxE54pfGkfzhuTQEh3mpcghrx95dL42df3Ei9s3DHCI2Q/yDChUMY3eeqzHUNAt9fPQeDZsMG/aHUrh775bebh28M8zSkUYzv6x1diPnDtywXGutW7MKGak6jw8F+45J6TW4IxNFYMmuWyBu9HOd5VkqWRKvezvqc8= rvm: - - 2.0.0 - - 2.2.0 + - 2.2 + - 2.3 - ruby-head - + matrix: allow_failures: - rvm: ruby-head fast_finish: true - + script: bundle exec rake travis notifications: diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a5dbe08..54d82fa0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog ## develop (unreleased) +### New features +* [#301](https://github.com/trema/pio/pull/301): Add `NiciraConjunction` action. +* [#300](https://github.com/trema/pio/pull/300): Add new classes `Pio::Match::PacketReg{0..3}`. ## 0.30.0 (11/17/2015) ### New features diff --git a/Gemfile b/Gemfile index 6e18d20a..a032b66b 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,35 @@ source 'https://rubygems.org' -gemspec development_group: :test +gemspec + +gem 'rake' + +group :development, :test do + gem 'aruba', require: false + gem 'codeclimate-test-reporter', require: false + gem 'coveralls', require: false + gem 'cucumber', require: false + gem 'flay', require: false + gem 'flog', require: false + gem 'reek', require: false + gem 'rspec', require: false + gem 'rspec-given', require: false + gem 'rubocop', require: false +end + +group :development do + gem 'guard', require: false + gem 'guard-bundler', require: false + gem 'guard-cucumber', require: false + gem 'guard-rspec', require: false + gem 'guard-rubocop', require: false + gem 'inch', require: false + gem 'listen', require: false + gem 'pry', require: false + gem 'rb-fchange', require: false + gem 'rb-fsevent', require: false + gem 'rb-inotify', require: false + gem 'relish', require: false + gem 'terminal-notifier-guard', require: false + gem 'yard', require: false +end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 00000000..224164c6 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,245 @@ +PATH + remote: . + specs: + pio (0.30.1) + activesupport (~> 5.0.2) + bindata (~> 2.1.0) + +GEM + remote: https://rubygems.org/ + specs: + activesupport (5.0.2) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + archive-tar-minitar (0.5.2) + aruba (0.14.2) + childprocess (~> 0.5.6) + contracts (~> 0.9) + cucumber (>= 1.3.19) + ffi (~> 1.9.10) + rspec-expectations (>= 2.99) + thor (~> 0.19) + ast (2.3.0) + axiom-types (0.1.1) + descendants_tracker (~> 0.0.4) + ice_nine (~> 0.11.0) + thread_safe (~> 0.3, >= 0.3.1) + bindata (2.1.0) + builder (3.2.2) + childprocess (0.5.9) + ffi (~> 1.0, >= 1.0.11) + codeclimate-engine-rb (0.3.1) + virtus (~> 1.0) + codeclimate-test-reporter (0.6.0) + simplecov (>= 0.7.1, < 1.0.0) + coderay (1.1.1) + coercible (1.0.0) + descendants_tracker (~> 0.0.1) + concurrent-ruby (1.0.5) + contracts (0.14.0) + coveralls (0.8.15) + json (>= 1.8, < 3) + simplecov (~> 0.12.0) + term-ansicolor (~> 1.3) + thor (~> 0.19.1) + tins (>= 1.6.0, < 2) + cucumber (2.4.0) + builder (>= 2.1.2) + cucumber-core (~> 1.5.0) + cucumber-wire (~> 0.0.1) + diff-lcs (>= 1.1.3) + gherkin (~> 4.0) + multi_json (>= 1.7.5, < 2.0) + multi_test (>= 0.1.2) + cucumber-core (1.5.0) + gherkin (~> 4.0) + cucumber-wire (0.0.1) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + diff-lcs (1.2.5) + docile (1.1.5) + domain_name (0.5.20160309) + unf (>= 0.0.5, < 1.0.0) + equalizer (0.0.11) + erubis (2.7.0) + ffi (1.9.14) + flay (2.8.1) + erubis (~> 2.7.0) + path_expander (~> 1.0) + ruby_parser (~> 3.0) + sexp_processor (~> 4.0) + flog (4.4.0) + path_expander (~> 1.0) + ruby_parser (~> 3.1, > 3.1.0) + sexp_processor (~> 4.4) + formatador (0.2.5) + gherkin (4.0.0) + given_core (3.8.0) + sorcerer (>= 0.3.7) + guard (2.14.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (~> 1.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-bundler (2.1.0) + bundler (~> 1.0) + guard (~> 2.2) + guard-compat (~> 1.1) + guard-compat (1.2.1) + guard-cucumber (2.1.2) + cucumber (~> 2.0) + guard-compat (~> 1.0) + nenv (~> 0.1) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) + guard-rubocop (1.2.0) + guard (~> 2.0) + rubocop (~> 0.20) + http-cookie (1.0.2) + domain_name (~> 0.5) + i18n (0.8.1) + ice_nine (0.11.2) + inch (0.7.1) + pry + sparkr (>= 0.2.0) + term-ansicolor + yard (~> 0.8.7.5) + json (2.0.2) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + lumberjack (1.0.10) + method_source (0.8.2) + mime-types (2.99.1) + minitest (5.10.1) + multi_json (1.12.1) + multi_test (0.1.2) + nenv (0.3.0) + netrc (0.11.0) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) + parser (2.3.1.4) + ast (~> 2.2) + path_expander (1.0.0) + powerpack (0.1.1) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + rainbow (2.1.0) + rake (11.3.0) + rb-fchange (0.0.6) + ffi + rb-fsevent (0.9.7) + rb-inotify (0.9.7) + ffi (>= 0.5.0) + reek (4.4.2) + codeclimate-engine-rb (~> 0.3.1) + parser (~> 2.3.1, >= 2.3.1.2) + rainbow (~> 2.0) + relish (0.7.1) + archive-tar-minitar (>= 0.5.2) + json (>= 1.4.6) + rest-client (>= 1.7.2) + rest-client (1.8.0) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 3.0) + netrc (~> 0.7) + rspec (3.5.0) + rspec-core (~> 3.5.0) + rspec-expectations (~> 3.5.0) + rspec-mocks (~> 3.5.0) + rspec-core (3.5.4) + rspec-support (~> 3.5.0) + rspec-expectations (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-given (3.8.0) + given_core (= 3.8.0) + rspec (>= 2.14.0) + rspec-mocks (3.5.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.5.0) + rspec-support (3.5.0) + rubocop (0.38.0) + parser (>= 2.3.0.6, < 3.0) + powerpack (~> 0.1) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.7) + unicode-display_width (~> 1.0, >= 1.0.1) + ruby-progressbar (1.7.5) + ruby_dep (1.4.0) + ruby_parser (3.8.2) + sexp_processor (~> 4.1) + sexp_processor (4.7.0) + shellany (0.0.1) + simplecov (0.12.0) + docile (~> 1.1.0) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.0) + slop (3.6.0) + sorcerer (1.0.2) + sparkr (0.4.1) + term-ansicolor (1.4.0) + tins (~> 1.0) + terminal-notifier-guard (1.7.0) + thor (0.19.1) + thread_safe (0.3.5) + tins (1.12.0) + tzinfo (1.2.3) + thread_safe (~> 0.1) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.2) + unicode-display_width (1.0.1) + virtus (1.0.5) + axiom-types (~> 0.1) + coercible (~> 1.0) + descendants_tracker (~> 0.0, >= 0.0.3) + equalizer (~> 0.0, >= 0.0.9) + yard (0.8.7.6) + +PLATFORMS + ruby + +DEPENDENCIES + aruba + codeclimate-test-reporter + coveralls + cucumber + flay + flog + guard + guard-bundler + guard-cucumber + guard-rspec + guard-rubocop + inch + listen + pio! + pry + rake + rb-fchange + rb-fsevent + rb-inotify + reek + relish + rspec + rspec-given + rubocop + terminal-notifier-guard + yard + +BUNDLED WITH + 1.14.6 diff --git a/Guardfile b/Guardfile index b8cb0737..0709b6d4 100644 --- a/Guardfile +++ b/Guardfile @@ -14,7 +14,7 @@ guard :rubocop do watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } end -guard :cucumber, cli: '--profile default' do +guard :cucumber, cmd_additional_args: '--profile default' do watch(%r{^features/.+\.feature$}) watch(%r{^features/support/.+$}) { 'features' } watch(%r{^features/step_definitions/(.+)_steps\.rb$}) do |m| diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c3b05826..00000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. {http://fsf.org/} - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see {http://www.gnu.org/licenses/}. - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - pio Copyright (C) 2013 Trema - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -{http://www.gnu.org/licenses/}. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -{http://www.gnu.org/philosophy/why-not-lgpl.html}. diff --git a/README.md b/README.md index 511dc030..009f0d3d 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,6 @@ Gitter Chat Inline docs -pio pencil - Pio is a ruby gem to easily parse and generate network packets. ## Features Overview @@ -22,47 +20,9 @@ Pio is a ruby gem to easily parse and generate network packets. [BinData](https://github.com/dmendel/bindata)'s declarative binary format DSL so that it is easy to read and debug by human beings. -## Supported packet formats - -Pio supports the following packet formats: - -- [ICMP](https://relishapp.com/trema/pio/docs/pio-icmp) -- [ARP](https://relishapp.com/trema/pio/docs/pio-arp) -- [LLDP](https://relishapp.com/trema/pio/docs/pio-lldp) -- [DHCP](https://relishapp.com/trema/pio/docs/pio-dhcp) -- [UDP](https://relishapp.com/trema/pio/docs/pio-udp) -- OpenFlow 1.0 - - [Hello](https://relishapp.com/trema/pio/docs/open-flow10/pio-hello) - - [HelloFailed](https://relishapp.com/trema/pio/docs/open-flow10/pio-error-hellofailed) - - [BadRequest](https://relishapp.com/trema/pio/docs/open-flow10/pio-error-badrequest) - - [Echo Request](https://relishapp.com/trema/pio/docs/open-flow10/pio-echo-request) - - [Echo Reply](https://relishapp.com/trema/pio/docs/open-flow10/pio-echo-reply) - - [Features Request](https://relishapp.com/trema/pio/docs/open-flow10/pio-features-request) - - [Features Reply](https://relishapp.com/trema/pio/docs/open-flow10/pio-features-reply) - - [Packet In](https://relishapp.com/trema/pio/docs/open-flow10/pio-packetin) - - [Packet Out](https://relishapp.com/trema/pio/docs/open-flow10/pio-packetout) - - [Flow Mod](https://relishapp.com/trema/pio/docs/open-flow10/pio-flowmod) - - [Port Status](https://relishapp.com/trema/pio/docs/open-flow10/pio-portstatus) - - [Exact Match](https://relishapp.com/trema/pio/docs/open-flow10/pio-exactmatch) - - [Barrier Request](https://relishapp.com/trema/pio/docs/open-flow10/pio-barrier-request) - - [Barrier Reply](https://relishapp.com/trema/pio/docs/open-flow10/pio-barrier-reply) - - [Description Stats Request](https://relishapp.com/trema/pio/docs/open-flow10/pio-descriptionstats-request) - - [Description Stats Reply](https://relishapp.com/trema/pio/docs/open-flow10/pio-descriptionstats-reply) - - [Flow Stats Request](https://relishapp.com/trema/pio/docs/open-flow10/pio-flowstats-request) - - [Flow Stats Reply](https://relishapp.com/trema/pio/docs/open-flow10/pio-flowstats-reply) - - [Aggregate Stats Request](https://relishapp.com/trema/pio/docs/open-flow10/pio-aggregatestats-request) - - [Aggregate Stats Reply](https://relishapp.com/trema/pio/docs/open-flow10/pio-aggregatestats-reply) -- OpenFlow 1.3 - - [Hello](https://relishapp.com/trema/pio/docs/open-flow13/pio-hello) - - [HelloFailed](https://relishapp.com/trema/pio/docs/open-flow13/pio-error-hellofailed) - - [BadRequest](https://relishapp.com/trema/pio/docs/open-flow13/pio-error-badrequest) - - [Echo Request](https://relishapp.com/trema/pio/docs/open-flow13/pio-echo-request) - - [Echo Reply](https://relishapp.com/trema/pio/docs/open-flow13/pio-echo-reply) - - [Features Request](https://relishapp.com/trema/pio/docs/open-flow13/pio-features-request) - - [Features Reply](https://relishapp.com/trema/pio/docs/open-flow13/pio-features-reply) - - [Packet In](https://relishapp.com/trema/pio/docs/open-flow13/pio-packetin) - - [Packet Out](https://relishapp.com/trema/pio/docs/open-flow13/pio-packetout) - - [Flow Mod](https://relishapp.com/trema/pio/docs/open-flow13/pio-flowmod) +## Documentation + +See https://relishapp.com/trema/pio/docs for links to documentation for all APIs. ## Installation @@ -80,10 +40,6 @@ and install it by running Bundler: bundle ``` -## Documents - -- [API document](https://relishapp.com/trema/pio/docs) - ## Team - [Yasuhito Takamiya](https://github.com/yasuhito) ([@yasuhito](https://twitter.com/yasuhito)) @@ -100,6 +56,7 @@ bundle ## License -Pio is released under the GNU General Public License version 3.0: +Pio is released under the GNU General Public License version 2.0 or MIT License: -- +* http://www.gnu.org/licenses/gpl-2.0.html +* http://www.opensource.org/licenses/MIT diff --git a/Rakefile b/Rakefile index f2066f58..4fe0a033 100644 --- a/Rakefile +++ b/Rakefile @@ -1,7 +1,7 @@ require 'bundler/gem_tasks' -RELISH_PROJECT = 'trema/pio' -FLAY_THRESHOLD = 1343 +RELISH_PROJECT = 'trema/pio'.freeze +FLAY_THRESHOLD = 1446 task default: :travis task test: [:spec, :cucumber] @@ -24,7 +24,7 @@ end desc 'Dump packet data file in Array' task :dump do unless ENV['PACKET_FILE'] - fail 'Usage: rake PACKET_FILE="foobar.{pcap,raw}" dump' + raise 'Usage: rake PACKET_FILE="foobar.{pcap,raw}" dump' end packet_file = File.join(File.dirname(__FILE__), 'features/', ENV['PACKET_FILE']) @@ -38,6 +38,6 @@ task :dump do end end else - fail "Unsupported file extension: #{ENV['PACKET_FILE']}" + raise "Unsupported file extension: #{ENV['PACKET_FILE']}" end end diff --git a/features/.nav b/features/.nav index 501a3aa4..0c4154a4 100644 --- a/features/.nav +++ b/features/.nav @@ -1,5 +1,13 @@ -- icmp.feature -- arp.feature +- ethernet_header.feature +- ipv4_header.feature +- arp: + - arp.feature + - arp_request.feature + - arp_reply.feature +- icmp: + - icmp.feature + - icmp_request.feature + - icmp_reply.feature - lldp.feature - dhcp.feature - udp.feature @@ -12,9 +20,10 @@ - features_request.feature - features_reply.feature - packet_in.feature + - flow_removed.feature + - port_status.feature - packet_out.feature - flow_mod.feature - - port_status.feature - exact_match.feature - open_flow13: - hello.feature diff --git a/features/arp-storm.pcap b/features/arp-storm.pcap deleted file mode 100644 index 20a10599..00000000 Binary files a/features/arp-storm.pcap and /dev/null differ diff --git a/features/arp.pcap b/features/arp.pcap deleted file mode 100644 index 94280940..00000000 Binary files a/features/arp.pcap and /dev/null differ diff --git a/features/arp.feature b/features/arp/arp.feature similarity index 59% rename from features/arp.feature rename to features/arp/arp.feature index 88b59ad1..2dd6731f 100644 --- a/features/arp.feature +++ b/features/arp/arp.feature @@ -1,15 +1,13 @@ -Feature: Pio::Arp - Scenario: create an ARP request - When I try to create a packet with: - """ - Pio::Arp::Request.new( - source_mac: '00:26:82:eb:ea:d1', - sender_protocol_address: '192.168.83.3', - target_protocol_address: '192.168.83.254' - ) +Feature: Arp + Background: + Given I use the fixture "arp" + + Scenario: read an ARP request packet + When I create a packet with: + """ruby + Pio::Arp.read(eval(IO.read('arp_request.rb'))) """ - Then it should finish successfully - And the packet has the following fields and values: + Then the packet has the following fields and values: | field | value | | class | Pio::Arp::Request | | destination_mac | ff:ff:ff:ff:ff:ff | @@ -25,18 +23,12 @@ Feature: Pio::Arp | target_hardware_address | 00:00:00:00:00:00 | | target_protocol_address | 192.168.83.254 | - Scenario: create an ARP reply - When I try to create a packet with: - """ - Pio::Arp::Reply.new( - source_mac: '00:16:9d:1d:9c:c4', - destination_mac: '00:26:82:eb:ea:d1', - sender_protocol_address: '192.168.83.254', - target_protocol_address: '192.168.83.3' - ) + Scenario: read an ARP reply packet + When I create a packet with: + """ruby + Pio::Arp.read(eval(IO.read('arp_reply.rb'))) """ - Then it should finish successfully - And the packet has the following fields and values: + Then the packet has the following fields and values: | field | value | | class | Pio::Arp::Reply | | destination_mac | 00:26:82:eb:ea:d1 | @@ -51,11 +43,3 @@ Feature: Pio::Arp | sender_protocol_address | 192.168.83.254 | | target_hardware_address | 00:26:82:eb:ea:d1 | | target_protocol_address | 192.168.83.3 | - - Scenario: parse arp.pcap - When I try to parse a file named "arp.pcap" with "Pio::Arp" class - Then it should finish successfully - - Scenario: parse arp-storm.pcap - When I try to parse a file named "arp-storm.pcap" with "Pio::Arp" class - Then it should finish successfully diff --git a/features/arp/arp_reply.feature b/features/arp/arp_reply.feature new file mode 100644 index 00000000..e1888eb4 --- /dev/null +++ b/features/arp/arp_reply.feature @@ -0,0 +1,69 @@ +Feature: Arp::Reply + Scenario: create an ARP reply + When I create a packet with: + """ruby + Pio::Arp::Reply.new( + destination_mac: '00:26:82:eb:ea:d1', + source_mac: '00:16:9d:1d:9c:c4', + sender_protocol_address: '192.168.83.254', + target_protocol_address: '192.168.83.3' + ) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::Arp::Reply | + | destination_mac | 00:26:82:eb:ea:d1 | + | source_mac | 00:16:9d:1d:9c:c4 | + | ether_type | 2054 | + | hardware_type | 1 | + | protocol_type | 2048 | + | hardware_length | 6 | + | protocol_length | 4 | + | operation | 2 | + | sender_hardware_address | 00:16:9d:1d:9c:c4 | + | sender_protocol_address | 192.168.83.254 | + | target_hardware_address | 00:26:82:eb:ea:d1 | + | target_protocol_address | 192.168.83.3 | + + Scenario: convert to Ruby code + When I eval the following Ruby code: + """ruby + Pio::Arp::Reply.new( + destination_mac: '00:26:82:eb:ea:d1', + source_mac: '00:16:9d:1d:9c:c4', + sender_protocol_address: '192.168.83.254', + target_protocol_address: '192.168.83.3' + ).to_ruby + """ + Then the result of eval should be: + """ruby + [ + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # destination_mac + 0x00, 0x16, 0x9d, 0x1d, 0x9c, 0xc4, # source_mac + 0x08, 0x06, # ether_type + 0x00, 0x01, # hardware_type + 0x08, 0x00, # protocol_type + 0x06, # hardware_length + 0x04, # protocol_length + 0x00, 0x02, # operation + 0x00, 0x16, 0x9d, 0x1d, 0x9c, 0xc4, # sender_hardware_address + 0xc0, 0xa8, 0x53, 0xfe, # sender_protocol_address + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # target_hardware_address + 0xc0, 0xa8, 0x53, 0x03, # target_protocol_address + ].pack('C42') + """ + + Scenario: ARP reply inspection + When I eval the following Ruby code: + """ruby + Pio::Arp::Reply.new( + destination_mac: '00:26:82:eb:ea:d1', + source_mac: '00:16:9d:1d:9c:c4', + sender_protocol_address: '192.168.83.254', + target_protocol_address: '192.168.83.3' + ).inspect + """ + Then the result of eval should be: + """ruby + # + """ diff --git a/features/arp/arp_request.feature b/features/arp/arp_request.feature new file mode 100644 index 00000000..9b6b5b0d --- /dev/null +++ b/features/arp/arp_request.feature @@ -0,0 +1,67 @@ +Feature: Arp::Request + Scenario: create an ARP request + When I create a packet with: + """ruby + Pio::Arp::Request.new( + source_mac: '00:26:82:eb:ea:d1', + sender_protocol_address: '192.168.83.3', + target_protocol_address: '192.168.83.254' + ) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::Arp::Request | + | destination_mac | ff:ff:ff:ff:ff:ff | + | source_mac | 00:26:82:eb:ea:d1 | + | ether_type | 2054 | + | hardware_type | 1 | + | protocol_type | 2048 | + | hardware_length | 6 | + | protocol_length | 4 | + | operation | 1 | + | sender_hardware_address | 00:26:82:eb:ea:d1 | + | sender_protocol_address | 192.168.83.3 | + | target_hardware_address | 00:00:00:00:00:00 | + | target_protocol_address | 192.168.83.254 | + + Scenario: convert to Ruby code + When I eval the following Ruby code: + """ruby + Pio::Arp::Request.new( + source_mac: '00:26:82:eb:ea:d1', + sender_protocol_address: '192.168.83.3', + target_protocol_address: '192.168.83.254' + ).to_ruby + """ + Then the result of eval should be: + """ruby + [ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac + 0x08, 0x06, # ether_type + 0x00, 0x01, # hardware_type + 0x08, 0x00, # protocol_type + 0x06, # hardware_length + 0x04, # protocol_length + 0x00, 0x01, # operation + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # sender_hardware_address + 0xc0, 0xa8, 0x53, 0x03, # sender_protocol_address + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # target_hardware_address + 0xc0, 0xa8, 0x53, 0xfe, # target_protocol_address + ].pack('C42') + """ + + Scenario: ARP request inspection + When I eval the following Ruby code: + """ruby + Pio::Arp::Request.new( + source_mac: '00:26:82:eb:ea:d1', + sender_protocol_address: '192.168.83.3', + target_protocol_address: '192.168.83.254' + ).inspect + """ + Then the result of eval should be: + """ + # + """ + diff --git a/features/dhcp.feature b/features/dhcp.feature index dc9d8741..d34c67e5 100644 --- a/features/dhcp.feature +++ b/features/dhcp.feature @@ -1,6 +1,6 @@ -Feature: Pio::Dhcp +Feature: Dhcp Scenario: create a DHCP Ack - When I try to create a packet with: + When I create a packet with: """ Pio::Dhcp::Ack.new( source_mac: 'aa:bb:cc:dd:ee:ff', @@ -14,95 +14,93 @@ Feature: Pio::Dhcp subnet_mask: '255.255.255.0' ) """ - Then it should finish successfully - And the packet has the following fields and values: - | field | value | - | class | Pio::Dhcp::Ack | - | destination_mac | 11:22:33:44:55:66 | - | source_mac | aa:bb:cc:dd:ee:ff | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 328 | - | ip_identifier | 0 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 128 | - | ip_protocol | 17 | - | ip_header_checksum | 47177 | - | source_ip_address | 192.168.0.10 | - | destination_ip_address | 192.168.0.1 | - | udp_source_port | 67 | - | udp_destination_port | 68 | - | udp_length | 308 | - | udp_checksum | 7012 | - | message_type | 5 | - | hw_addr_type | 1 | - | hw_addr_len | 6 | - | hops | 0 | - | transaction_id | 3735928559 | - | seconds | 0 | - | bootp_flags | 0 | - | client_ip_address | 0.0.0.0 | - | your_ip_address | 192.168.0.1 | - | next_server_ip_address | 0.0.0.0 | - | relay_agent_ip_address | 0.0.0.0 | - | client_mac_address | aa:bb:cc:dd:ee:ff | - | subnet_mask | 255.255.255.0 | - | server_identifier | 192.168.0.10 | - | renewal_time_value | 3735928559 | - | rebinding_time_value | 3735928559 | - | ip_address_lease_time | 3735928559 | + Then the packet has the following fields and values: + | field | value | + | class | Pio::Dhcp::Ack | + | destination_mac | 11:22:33:44:55:66 | + | source_mac | aa:bb:cc:dd:ee:ff | + | ether_type | 2048 | + | ip_version | 4 | + | ip_header_length | 5 | + | ip_type_of_service | 0 | + | ip_total_length | 328 | + | ip_identifier | 0 | + | ip_flag | 0 | + | ip_fragment | 0 | + | ip_ttl | 128 | + | ip_protocol | 17 | + | ip_header_checksum | 47177 | + | source_ip_address | 192.168.0.10 | + | destination_ip_address | 192.168.0.1 | + | udp_source_port | 67 | + | udp_destination_port | 68 | + | udp_length | 308 | + | udp_checksum | 7012 | + | message_type | 5 | + | hw_addr_type | 1 | + | hw_addr_len | 6 | + | hops | 0 | + | transaction_id | 3735928559 | + | seconds | 0 | + | bootp_flags | 0 | + | client_ip_address | 0.0.0.0 | + | your_ip_address | 192.168.0.1 | + | next_server_ip_address | 0.0.0.0 | + | relay_agent_ip_address | 0.0.0.0 | + | client_mac_address | aa:bb:cc:dd:ee:ff | + | subnet_mask | 255.255.255.0 | + | server_identifier | 192.168.0.10 | + | renewal_time_value | 3735928559 | + | rebinding_time_value | 3735928559 | + | ip_address_lease_time | 3735928559 | Scenario: create a DHCP Discover - When I try to create a packet with: + When I create a packet with: """ Pio::Dhcp::Discover.new( source_mac: '24:db:ac:41:e5:5b', transaction_id: 0xdeadbeef ) """ - Then it should finish successfully - And the packet has the following fields and values: - | field | value | - | class | Pio::Dhcp::Discover | - | destination_mac | ff:ff:ff:ff:ff:ff | - | source_mac | 24:db:ac:41:e5:5b | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 328 | - | ip_identifier | 0 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 128 | - | ip_protocol | 17 | - | ip_header_checksum | 14758 | - | source_ip_address | 0.0.0.0 | - | destination_ip_address | 255.255.255.255 | - | udp_source_port | 68 | - | udp_destination_port | 67 | - | udp_length | 308 | - | udp_checksum | 34836 | - | message_type | 1 | - | hw_addr_type | 1 | - | hw_addr_len | 6 | - | hops | 0 | - | transaction_id | 3735928559 | - | seconds | 0 | - | bootp_flags | 0 | - | client_ip_address | 0.0.0.0 | - | your_ip_address | 0.0.0.0 | - | next_server_ip_address | 0.0.0.0 | - | relay_agent_ip_address | 0.0.0.0 | - | client_mac_address | 24:db:ac:41:e5:5b | - | parameters_list | [1, 3, 6, 42] | - | client_identifier | 24:db:ac:41:e5:5b | + Then the packet has the following fields and values: + | field | value | + | class | Pio::Dhcp::Discover | + | destination_mac | ff:ff:ff:ff:ff:ff | + | source_mac | 24:db:ac:41:e5:5b | + | ether_type | 2048 | + | ip_version | 4 | + | ip_header_length | 5 | + | ip_type_of_service | 0 | + | ip_total_length | 328 | + | ip_identifier | 0 | + | ip_flag | 0 | + | ip_fragment | 0 | + | ip_ttl | 128 | + | ip_protocol | 17 | + | ip_header_checksum | 14758 | + | source_ip_address | 0.0.0.0 | + | destination_ip_address | 255.255.255.255 | + | udp_source_port | 68 | + | udp_destination_port | 67 | + | udp_length | 308 | + | udp_checksum | 34836 | + | message_type | 1 | + | hw_addr_type | 1 | + | hw_addr_len | 6 | + | hops | 0 | + | transaction_id | 3735928559 | + | seconds | 0 | + | bootp_flags | 0 | + | client_ip_address | 0.0.0.0 | + | your_ip_address | 0.0.0.0 | + | next_server_ip_address | 0.0.0.0 | + | relay_agent_ip_address | 0.0.0.0 | + | client_mac_address | 24:db:ac:41:e5:5b | + | parameters_list | [1, 3, 6, 42] | + | client_identifier | 24:db:ac:41:e5:5b | Scenario: create a DHCP Request - When I try to create a packet with: + When I create a packet with: """ Pio::Dhcp::Request.new( source_mac: '24:db:ac:41:e5:5b', @@ -111,47 +109,46 @@ Feature: Pio::Dhcp requested_ip_address: '192.168.0.10' ) """ - Then it should finish successfully - And the packet has the following fields and values: - | field | value | - | class | Pio::Dhcp::Request | - | destination_mac | ff:ff:ff:ff:ff:ff | - | source_mac | 24:db:ac:41:e5:5b | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 328 | - | ip_identifier | 0 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 128 | - | ip_protocol | 17 | - | ip_header_checksum | 14758 | - | source_ip_address | 0.0.0.0 | - | destination_ip_address | 255.255.255.255 | - | udp_source_port | 68 | - | udp_destination_port | 67 | - | udp_length | 308 | - | udp_checksum | 52915 | - | message_type | 3 | - | hw_addr_type | 1 | - | hw_addr_len | 6 | - | hops | 0 | - | transaction_id | 3735928559 | - | seconds | 0 | - | bootp_flags | 0 | - | client_ip_address | 0.0.0.0 | - | your_ip_address | 0.0.0.0 | - | next_server_ip_address | 0.0.0.0 | - | relay_agent_ip_address | 0.0.0.0 | - | client_mac_address | 24:db:ac:41:e5:5b | - | parameters_list | [1, 3, 6, 42] | - | client_identifier | 24:db:ac:41:e5:5b | - | requested_ip_address | 192.168.0.10 | + Then the packet has the following fields and values: + | field | value | + | class | Pio::Dhcp::Request | + | destination_mac | ff:ff:ff:ff:ff:ff | + | source_mac | 24:db:ac:41:e5:5b | + | ether_type | 2048 | + | ip_version | 4 | + | ip_header_length | 5 | + | ip_type_of_service | 0 | + | ip_total_length | 328 | + | ip_identifier | 0 | + | ip_flag | 0 | + | ip_fragment | 0 | + | ip_ttl | 128 | + | ip_protocol | 17 | + | ip_header_checksum | 14758 | + | source_ip_address | 0.0.0.0 | + | destination_ip_address | 255.255.255.255 | + | udp_source_port | 68 | + | udp_destination_port | 67 | + | udp_length | 308 | + | udp_checksum | 52915 | + | message_type | 3 | + | hw_addr_type | 1 | + | hw_addr_len | 6 | + | hops | 0 | + | transaction_id | 3735928559 | + | seconds | 0 | + | bootp_flags | 0 | + | client_ip_address | 0.0.0.0 | + | your_ip_address | 0.0.0.0 | + | next_server_ip_address | 0.0.0.0 | + | relay_agent_ip_address | 0.0.0.0 | + | client_mac_address | 24:db:ac:41:e5:5b | + | parameters_list | [1, 3, 6, 42] | + | client_identifier | 24:db:ac:41:e5:5b | + | requested_ip_address | 192.168.0.10 | Scenario: create a DHCP Offer - When I try to create a packet with: + When I create a packet with: """ Pio::Dhcp::Offer.new( source_mac: 'aa:bb:cc:dd:ee:ff', @@ -165,47 +162,45 @@ Feature: Pio::Dhcp subnet_mask: '255.255.255.0' ) """ - Then it should finish successfully - And the packet has the following fields and values: - | field | value | - | class | Pio::Dhcp::Offer | - | destination_mac | 11:22:33:44:55:66 | - | source_mac | aa:bb:cc:dd:ee:ff | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 328 | - | ip_identifier | 0 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 128 | - | ip_protocol | 17 | - | ip_header_checksum | 47177 | - | source_ip_address | 192.168.0.10 | - | destination_ip_address | 192.168.0.1 | - | udp_source_port | 67 | - | udp_destination_port | 68 | - | udp_length | 308 | - | udp_checksum | 7780 | - | message_type | 2 | - | hw_addr_type | 1 | - | hw_addr_len | 6 | - | hops | 0 | - | transaction_id | 3735928559 | - | seconds | 0 | - | bootp_flags | 0 | - | client_ip_address | 0.0.0.0 | - | your_ip_address | 192.168.0.1 | - | next_server_ip_address | 0.0.0.0 | - | relay_agent_ip_address | 0.0.0.0 | - | client_mac_address | aa:bb:cc:dd:ee:ff | - | subnet_mask | 255.255.255.0 | - | server_identifier | 192.168.0.10 | - | renewal_time_value | 3735928559 | - | rebinding_time_value | 3735928559 | - | ip_address_lease_time | 3735928559 | + Then the packet has the following fields and values: + | field | value | + | class | Pio::Dhcp::Offer | + | destination_mac | 11:22:33:44:55:66 | + | source_mac | aa:bb:cc:dd:ee:ff | + | ether_type | 2048 | + | ip_version | 4 | + | ip_header_length | 5 | + | ip_type_of_service | 0 | + | ip_total_length | 328 | + | ip_identifier | 0 | + | ip_flag | 0 | + | ip_fragment | 0 | + | ip_ttl | 128 | + | ip_protocol | 17 | + | ip_header_checksum | 47177 | + | source_ip_address | 192.168.0.10 | + | destination_ip_address | 192.168.0.1 | + | udp_source_port | 67 | + | udp_destination_port | 68 | + | udp_length | 308 | + | udp_checksum | 7780 | + | message_type | 2 | + | hw_addr_type | 1 | + | hw_addr_len | 6 | + | hops | 0 | + | transaction_id | 3735928559 | + | seconds | 0 | + | bootp_flags | 0 | + | client_ip_address | 0.0.0.0 | + | your_ip_address | 192.168.0.1 | + | next_server_ip_address | 0.0.0.0 | + | relay_agent_ip_address | 0.0.0.0 | + | client_mac_address | aa:bb:cc:dd:ee:ff | + | subnet_mask | 255.255.255.0 | + | server_identifier | 192.168.0.10 | + | renewal_time_value | 3735928559 | + | rebinding_time_value | 3735928559 | + | ip_address_lease_time | 3735928559 | Scenario: parse dhcp.pcap - When I try to parse a file named "dhcp.pcap" with "Pio::Dhcp" class - Then it should finish successfully + Then I parse a file named "dhcp.pcap" with "Pio::Dhcp" class diff --git a/features/ethernet_header.feature b/features/ethernet_header.feature new file mode 100644 index 00000000..8656da83 --- /dev/null +++ b/features/ethernet_header.feature @@ -0,0 +1,149 @@ +Feature: EthernetHeader + Scenario: create an Ethernet header + When I create a packet with: + """ruby + Pio::EthernetHeader.new( + destination_mac: 'ff:ff:ff:ff:ff:ff', + source_mac: '00:26:82:eb:ea:d1', + ether_type: Pio::Ethernet::Type::IPV4 + ) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::EthernetHeader | + | destination_mac | ff:ff:ff:ff:ff:ff | + | source_mac | 00:26:82:eb:ea:d1 | + | ether_type.to_hex | 0x800 | + + Scenario: create a VLAN-tagged Ethernet header + When I create a packet with: + """ruby + Pio::EthernetHeader.new( + destination_mac: 'ff:ff:ff:ff:ff:ff', + source_mac: '00:26:82:eb:ea:d1', + ether_type: Pio::Ethernet::Type::VLAN, + vlan_pcp: 5, + vlan_cfi: 0, + vlan_vid: 100 + ) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::EthernetHeader | + | destination_mac | ff:ff:ff:ff:ff:ff | + | source_mac | 00:26:82:eb:ea:d1 | + | ether_type.to_hex | 0x8100 | + | vlan_pcp | 5 | + | vlan_cfi | 0 | + | vlan_vid | 100 | + + Scenario: read an Ethernet header + Given I use the fixture "ethernet_header" + When I create a packet with: + """ruby + Pio::EthernetHeader.read(eval(IO.read('ethernet_header.rb'))) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::EthernetHeader | + | destination_mac | ff:ff:ff:ff:ff:ff | + | source_mac | 00:26:82:eb:ea:d1 | + | ether_type.to_hex | 0x800 | + + Scenario: read a VLAN-tagged Ethernet header + Given I use the fixture "ethernet_header" + When I create a packet with: + """ruby + Pio::EthernetHeader.read(eval(IO.read('vlan_ethernet_header.rb'))) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::EthernetHeader | + | destination_mac | ff:ff:ff:ff:ff:ff | + | source_mac | 00:26:82:eb:ea:d1 | + | ether_type.to_hex | 0x8100 | + | vlan_pcp | 5 | + | vlan_cfi | 0 | + | vlan_vid | 100 | + + Scenario: convert Ethernet header to Ruby code + When I eval the following Ruby code: + """ruby + Pio::EthernetHeader.new( + destination_mac: 'ff:ff:ff:ff:ff:ff', + source_mac: '00:26:82:eb:ea:d1', + ether_type: Pio::Ethernet::Type::IPV4 + ).to_ruby + """ + Then the result of eval should be: + """ruby + [ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac + 0x08, 0x00, # ether_type + ].pack('C14') + """ + + Scenario: convert VLAN-tagged Ethernet header to Ruby code + When I eval the following Ruby code: + """ruby + Pio::EthernetHeader.new( + destination_mac: 'ff:ff:ff:ff:ff:ff', + source_mac: '00:26:82:eb:ea:d1', + ether_type: Pio::Ethernet::Type::VLAN, + vlan_pcp: 5, + vlan_cfi: 0, + vlan_vid: 100 + ).to_ruby + """ + Then the result of eval should be: + """ruby + [ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac + 0x81, 0x00, # ether_type + 0b101_0_000001100100, # vlan_pcp, vlan_cfi, vlan_vid + 0x81, 0x00, # ether_type_vlan + ].pack('C14nC2') + """ + + Scenario: EthernetHeader instance inspection + When I eval the following Ruby code: + """ruby + Pio::EthernetHeader.new( + destination_mac: 'ff:ff:ff:ff:ff:ff', + source_mac: '00:26:82:eb:ea:d1', + ether_type: Pio::Ethernet::Type::IPV4, + ).inspect + """ + Then the result of eval should be: + """ + # + """ + + Scenario: VLAN-tagged EthernetHeader instance inspection + When I eval the following Ruby code: + """ruby + Pio::EthernetHeader.new( + destination_mac: 'ff:ff:ff:ff:ff:ff', + source_mac: '00:26:82:eb:ea:d1', + ether_type: Pio::Ethernet::Type::VLAN, + vlan_pcp: 5, + vlan_cfi: 0, + vlan_vid: 100 + ).inspect + """ + Then the result of eval should be: + """ + # + """ + + Scenario: EthernetHeader class inspection + When I eval the following Ruby code: + """ruby + Pio::EthernetHeader.inspect + """ + Then the result of eval should be: + """ + Pio::EthernetHeader(destination_mac: mac_address, source_mac: mac_address, ether_type: ether_type, vlan_pcp: bit3, vlan_cfi: bit1, vlan_vid: bit12, ether_type_vlan: uint16) + """ diff --git a/features/icmp.feature b/features/icmp.feature deleted file mode 100644 index f2a530ae..00000000 --- a/features/icmp.feature +++ /dev/null @@ -1,130 +0,0 @@ -Feature: Pio::Icmp - Scenario: create an ICMP request - When I try to create a packet with: - """ - Pio::Icmp::Request.new( - source_mac: '00:16:9d:1d:9c:c4', - destination_mac: '00:26:82:eb:ea:d1', - source_ip_address: '192.168.83.3', - destination_ip_address: '192.168.83.254' - ) - """ - Then it should finish successfully - And the packet has the following fields and values: - | field | value | - | class | Pio::Icmp::Request | - | destination_mac | 00:26:82:eb:ea:d1 | - | source_mac | 00:16:9d:1d:9c:c4 | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 50 | - | ip_identifier | 0 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 128 | - | ip_protocol | 1 | - | ip_header_checksum | 4729 | - | source_ip_address | 192.168.83.3 | - | destination_ip_address | 192.168.83.254 | - | ip_option | | - | icmp_type | 8 | - | icmp_code | 0 | - | icmp_checksum | 63231 | - | icmp_identifier | 256 | - | icmp_sequence_number | 0 | - | echo_data | | - - Scenario: create an ICMP reply - When I try to create a packet with: - """ - Pio::Icmp::Reply.new( - source_mac: '00:26:82:eb:ea:d1', - destination_mac: '00:16:9d:1d:9c:c4', - source_ip_address: '192.168.83.254', - destination_ip_address: '192.168.83.3', - identifier: 256, - sequence_number: 0 - ) - """ - Then it should finish successfully - And the packet has the following fields and values: - | field | value | - | class | Pio::Icmp::Reply | - | destination_mac | 00:16:9d:1d:9c:c4 | - | source_mac | 00:26:82:eb:ea:d1 | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 50 | - | ip_identifier | 0 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 128 | - | ip_protocol | 1 | - | ip_header_checksum | 4729 | - | source_ip_address | 192.168.83.254 | - | destination_ip_address | 192.168.83.3 | - | ip_option | | - | icmp_type | 0 | - | icmp_code | 0 | - | icmp_checksum | 65279 | - | icmp_identifier | 256 | - | icmp_sequence_number | 0 | - | echo_data | | - - Scenario: parse icmp.pcap - When I try to parse a file named "icmp.pcap" with "Pio::Icmp" class - Then it should finish successfully - And the message #1 have the following fields and values: - | field | value | - | class | Pio::Icmp::Request | - | destination_mac | 00:13:46:0b:22:ba | - | source_mac | 00:16:ce:6e:8b:24 | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 60 | - | ip_identifier | 36507 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 128 | - | ip_protocol | 1 | - | ip_header_checksum | 10850 | - | source_ip_address | 192.168.0.114 | - | destination_ip_address | 192.168.0.1 | - | ip_option | | - | icmp_type | 8 | - | icmp_code | 0 | - | icmp_checksum | 18780 | - | icmp_identifier | 768 | - | icmp_sequence_number | 256 | - | echo_data | abcdefghijklmnopqrstuvwabcdefghi | - And the message #2 have the following fields and values: - | field | value | - | class | Pio::Icmp::Reply | - | destination_mac | 00:16:ce:6e:8b:24 | - | source_mac | 00:13:46:0b:22:ba | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 60 | - | ip_identifier | 24150 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 127 | - | ip_protocol | 1 | - | ip_header_checksum | 23463 | - | source_ip_address | 192.168.0.1 | - | destination_ip_address | 192.168.0.114 | - | ip_option | | - | icmp_type | 0 | - | icmp_code | 0 | - | icmp_checksum | 20828 | - | icmp_identifier | 768 | - | icmp_sequence_number | 256 | - | echo_data | abcdefghijklmnopqrstuvwabcdefghi | diff --git a/features/icmp.pcap b/features/icmp.pcap deleted file mode 100644 index c50548b1..00000000 Binary files a/features/icmp.pcap and /dev/null differ diff --git a/features/icmp/icmp.feature b/features/icmp/icmp.feature new file mode 100644 index 00000000..eabe4bc5 --- /dev/null +++ b/features/icmp/icmp.feature @@ -0,0 +1,47 @@ +Feature: Icmp + Background: + Given I use the fixture "icmp" + + Scenario: read an ICMP request + When I create a packet with: + """ruby + Pio::Icmp.read(eval(IO.read('icmp_request.rb'))) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::Icmp::Request | + | destination_mac | 11:22:33:44:55:66 | + | source_mac | 66:55:44:33:22:11 | + | ether_type.to_hex | 0x800 | + | ip_total_length | 54 | + | ip_protocol | 1 | + | source_ip_address | 192.168.83.3 | + | destination_ip_address | 192.168.83.254 | + | icmp_type | 8 | + | icmp_code | 0 | + | icmp_checksum | 26613 | + | icmp_identifier | 256 | + | icmp_sequence_number | 111 | + | echo_data | abcdefghijklmnopqrstuvwxyz | + + Scenario: read an ICMP reply + When I create a packet with: + """ruby + Pio::Icmp.read(eval(IO.read('icmp_reply.rb'))) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::Icmp::Reply | + | destination_mac | 11:22:33:44:55:66 | + | source_mac | 66:55:44:33:22:11 | + | ether_type.to_hex | 0x800 | + | ip_total_length | 54 | + | ip_protocol | 1 | + | source_ip_address | 192.168.83.254 | + | destination_ip_address | 192.168.83.3 | + | icmp_type | 0 | + | icmp_code | 0 | + | icmp_checksum | 28661 | + | icmp_identifier | 256 | + | icmp_sequence_number | 111 | + | echo_data | abcdefghijklmnopqrstuvwxyz | diff --git a/features/icmp/icmp_reply.feature b/features/icmp/icmp_reply.feature new file mode 100644 index 00000000..f46ea2ed --- /dev/null +++ b/features/icmp/icmp_reply.feature @@ -0,0 +1,93 @@ +Feature: Icmp::Reply + Scenario: create an ICMP reply + When I create a packet with: + """ + Pio::Icmp::Reply.new( + destination_mac: '11:22:33:44:55:66', + source_mac: '66:55:44:33:22:11', + source_ip_address: '192.168.83.254', + destination_ip_address: '192.168.83.3', + icmp_identifier: 256, + icmp_sequence_number: 111, + echo_data: 'hello world' + ) + """ + Then the packet has the following fields and values: + | field | value | + | destination_mac | 11:22:33:44:55:66 | + | source_mac | 66:55:44:33:22:11 | + | ether_type.to_hex | 0x800 | + | ip_total_length | 50 | + | ip_protocol | 1 | + | source_ip_address | 192.168.83.254 | + | destination_ip_address | 192.168.83.3 | + | icmp_type | 0 | + | icmp_code | 0 | + | icmp_checksum | 53442 | + | icmp_identifier | 256 | + | icmp_sequence_number | 111 | + | echo_data | hello world | + + Scenario: convert an ICMP reply to Ruby code + When I eval the following Ruby code: + """ruby + Pio::Icmp::Reply.new( + destination_mac: '11:22:33:44:55:66', + source_mac: '66:55:44:33:22:11', + source_ip_address: '192.168.83.254', + destination_ip_address: '192.168.83.3', + icmp_identifier: 256, + icmp_sequence_number: 0 + ).to_ruby + """ + Then the result of eval should be: + """ruby + [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, # destination_mac + 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, # source_mac + 0x08, 0x00, # ether_type + 0b0100_0101, # ip_version, ip_header_length + 0x00, # ip_type_of_service + 0x00, 0x32, # ip_total_length + 0x00, 0x00, # ip_identifier + 0b000_0000000000000, # ip_flag, ip_fragment + 0x80, # ip_ttl + 0x01, # ip_protocol + 0x12, 0x79, # ip_header_checksum + 0xc0, 0xa8, 0x53, 0xfe, # source_ip_address + 0xc0, 0xa8, 0x53, 0x03, # destination_ip_address + 0x00, # icmp_type + 0x00, # icmp_code + 0xfe, 0xff, # icmp_checksum + 0x01, 0x00, # icmp_identifier + 0x00, 0x00, # icmp_sequence_number + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # padding + ].pack('C20nC42') + """ + + Scenario: Icmp::Reply instance inspection + When I create a packet with: + """ + Pio::Icmp::Reply.new( + destination_mac: '00:26:82:eb:ea:d1', + source_mac: '00:16:9d:1d:9c:c4', + source_ip_address: '1.2.3.4', + destination_ip_address: '4.3.2.1', + icmp_identifier: 256, + icmp_sequence_number: 0 + ).inspect + """ + Then the result of eval should be: + """ + # + """ + + Scenario: Icmp::Reply class inspection + When I eval the following Ruby code: + """ruby + Pio::Icmp::Reply.inspect + """ + Then the result of eval should be: + """ + Pio::Icmp::Reply(destination_mac: mac_address, source_mac: mac_address, ether_type: ether_type, vlan_pcp: bit3, vlan_cfi: bit1, vlan_vid: bit12, ether_type_vlan: uint16, ip_version: bit4, ip_header_length: bit4, ip_type_of_service: uint8, ip_total_length: uint16, ip_identifier: uint16, ip_flag: bit3, ip_fragment: bit13, ip_ttl: uint8, ip_protocol: uint8, ip_header_checksum: uint16, source_ip_address: ip_address, destination_ip_address: ip_address, ip_option: string, icmp_type: uint8, icmp_code: uint8, icmp_checksum: uint16, icmp_identifier: uint16, icmp_sequence_number: uint16, echo_data: string) + """ diff --git a/features/icmp/icmp_request.feature b/features/icmp/icmp_request.feature new file mode 100644 index 00000000..089e5dc5 --- /dev/null +++ b/features/icmp/icmp_request.feature @@ -0,0 +1,89 @@ +Feature: Icmp::Request + Scenario: create an ICMP request + When I create a packet with: + """ruby + Pio::Icmp::Request.new( + source_mac: '11:22:33:44:55:66', + destination_mac: '66:55:44:33:22:11', + source_ip_address: '192.168.83.3', + destination_ip_address: '192.168.83.254', + icmp_identifier: 256, + icmp_sequence_number: 111, + echo_data: 'hello world' + ) + """ + Then the packet has the following fields and values: + | field | value | + | source_mac | 11:22:33:44:55:66 | + | destination_mac | 66:55:44:33:22:11 | + | ether_type.to_hex | 0x800 | + | ip_total_length | 50 | + | ip_protocol | 1 | + | source_ip_address | 192.168.83.3 | + | destination_ip_address | 192.168.83.254 | + | icmp_type | 8 | + | icmp_code | 0 | + | icmp_checksum | 51394 | + | icmp_identifier | 256 | + | icmp_sequence_number | 111 | + | echo_data | hello world | + + Scenario: convert an ICMP request to Ruby code + When I eval the following Ruby code: + """ruby + Pio::Icmp::Request.new( + destination_mac: '11:22:33:44:55:66', + source_mac: '66:55:44:33:22:11', + source_ip_address: '192.168.83.3', + destination_ip_address: '192.168.83.254' + ).to_ruby + """ + Then the result of eval should be: + """ruby + [ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, # destination_mac + 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, # source_mac + 0x08, 0x00, # ether_type + 0b0100_0101, # ip_version, ip_header_length + 0x00, # ip_type_of_service + 0x00, 0x32, # ip_total_length + 0x00, 0x00, # ip_identifier + 0b000_0000000000000, # ip_flag, ip_fragment + 0x80, # ip_ttl + 0x01, # ip_protocol + 0x12, 0x79, # ip_header_checksum + 0xc0, 0xa8, 0x53, 0x03, # source_ip_address + 0xc0, 0xa8, 0x53, 0xfe, # destination_ip_address + 0x08, # icmp_type + 0x00, # icmp_code + 0xf7, 0xff, # icmp_checksum + 0x00, 0x00, # icmp_identifier + 0x00, 0x00, # icmp_sequence_number + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # padding + ].pack('C20nC42') + """ + + Scenario: Icmp::Request instance inspection + When I create a packet with: + """ruby + Pio::Icmp::Request.new( + destination_mac: '00:26:82:eb:ea:d1', + source_mac: '00:16:9d:1d:9c:c4', + source_ip_address: '1.2.3.4', + destination_ip_address: '4.3.2.1' + ).inspect + """ + Then the result of eval should be: + """ + # + """ + + Scenario: Icmp::Request class inspection + When I eval the following Ruby code: + """ruby + Pio::Icmp::Request.inspect + """ + Then the result of eval should be: + """ + Pio::Icmp::Request(destination_mac: mac_address, source_mac: mac_address, ether_type: ether_type, vlan_pcp: bit3, vlan_cfi: bit1, vlan_vid: bit12, ether_type_vlan: uint16, ip_version: bit4, ip_header_length: bit4, ip_type_of_service: uint8, ip_total_length: uint16, ip_identifier: uint16, ip_flag: bit3, ip_fragment: bit13, ip_ttl: uint8, ip_protocol: uint8, ip_header_checksum: uint16, source_ip_address: ip_address, destination_ip_address: ip_address, ip_option: string, icmp_type: uint8, icmp_code: uint8, icmp_checksum: uint16, icmp_identifier: uint16, icmp_sequence_number: uint16, echo_data: string) + """ diff --git a/features/ipv4_header.feature b/features/ipv4_header.feature new file mode 100644 index 00000000..78c993b4 --- /dev/null +++ b/features/ipv4_header.feature @@ -0,0 +1,89 @@ +Feature: IPv4Header + Scenario: create an IPv4 header + When I create a packet with: + """ruby + IPv4Header.new(source_ip_address: '1.2.3.4', + destination_ip_address: '4.3.2.1') + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::IPv4Header | + | ip_version | 4 | + | ip_header_length | 5 | + | ip_type_of_service | 0 | + | ip_total_length | 20 | + | ip_identifier | 0 | + | ip_flag | 0 | + | ip_fragment | 0 | + | ip_ttl | 128 | + | ip_protocol | 0 | + | ip_header_checksum | 12513 | + | source_ip_address | 1.2.3.4 | + | destination_ip_address | 4.3.2.1 | + | ip_option | | + + Scenario: read an IPv4 header + Given I use the fixture "ipv4_header" + When I create a packet with: + """ruby + Pio::IPv4Header.read(eval(IO.read('ipv4_header.rb'))) + """ + Then the packet has the following fields and values: + | field | value | + | class | Pio::IPv4Header | + | ip_version | 4 | + | ip_header_length | 5 | + | ip_type_of_service | 0 | + | ip_total_length | 20 | + | ip_identifier | 0 | + | ip_flag | 0 | + | ip_fragment | 0 | + | ip_ttl | 128 | + | ip_protocol | 0 | + | ip_header_checksum | 12513 | + | source_ip_address | 1.2.3.4 | + | destination_ip_address | 4.3.2.1 | + | ip_option | | + + Scenario: convert IPv4 header to Ruby code + When I eval the following Ruby code: + """ruby + IPv4Header.new(source_ip_address: '1.2.3.4', + destination_ip_address: '4.3.2.1').to_ruby + """ + Then the result of eval should be: + """ruby + [ + 0b0100_0101, # ip_version, ip_header_length + 0x00, # ip_type_of_service + 0x00, 0x14, # ip_total_length + 0x00, 0x00, # ip_identifier + 0b000_0000000000000, # ip_flag, ip_fragment + 0x80, # ip_ttl + 0x00, # ip_protocol + 0x30, 0xe1, # ip_header_checksum + 0x01, 0x02, 0x03, 0x04, # source_ip_address + 0x04, 0x03, 0x02, 0x01, # destination_ip_address + ].pack('C6nC12') + """ + + Scenario: IPv4Header instance inspection + When I eval the following Ruby code: + """ruby + IPv4Header.new(source_ip_address: '1.2.3.4', + destination_ip_address: '4.3.2.1').inspect + """ + Then the result of eval should be: + """ + # + """ + + Scenario: IPv4Header class inspection + When I eval the following Ruby code: + """ruby + Pio::IPv4Header.inspect + """ + Then the result of eval should be: + """ + IPv4Header(ip_version: bit4, ip_header_length: bit4, ip_type_of_service: uint8, ip_total_length: uint16, ip_identifier: uint16, ip_flag: bit3, ip_fragment: bit13, ip_ttl: uint8, ip_protocol: uint8, ip_header_checksum: uint16, source_ip_address: ip_address, destination_ip_address: ip_address, ip_option: string) + """ diff --git a/features/lldp.feature b/features/lldp.feature index 3375ffe4..e8123e89 100644 --- a/features/lldp.feature +++ b/features/lldp.feature @@ -1,11 +1,10 @@ -Feature: Pio::Lldp +Feature: Lldp Scenario: create - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Lldp.new(dpid: 0x123, port_number: 12, source_mac: '11:22:33:44:55:66') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | class | Pio::Lldp | | destination_mac | 01:80:c2:00:00:0e | @@ -23,8 +22,7 @@ Feature: Pio::Lldp | organizationally_specific | | Scenario: parse lldp.minimal.pcap - When I try to parse a file named "lldp.minimal.pcap" with "Pio::Lldp" class - Then it should finish successfully + When I parse a file named "lldp.minimal.pcap" with "Pio::Lldp" class Then the message #1 have the following fields and values: | field | value | | class | Pio::Lldp | @@ -43,5 +41,4 @@ Feature: Pio::Lldp | organizationally_specific | | Scenario: parse lldp.detailed.pcap - When I try to parse a file named "lldp.detailed.pcap" with "Pio::Lldp" class - Then it should finish successfully + Then I parse a file named "lldp.detailed.pcap" with "Pio::Lldp" class diff --git a/features/open_flow/header.feature b/features/open_flow/header.feature new file mode 100644 index 00000000..ae1dbe1c --- /dev/null +++ b/features/open_flow/header.feature @@ -0,0 +1,13 @@ +Feature: OpenFlow::Header + + Scenario: OpenFlow::Header#to_hex + When I create an OpenFlow message with: + """ + Pio::OpenFlow::Header.new(version: :OpenFlow10, + type: 10, + message_length: 18, + transaction_id: 0xff) + """ + Then the message has the following fields and values: + | field | value | + | to_bytes | 0x01, 0x0a, 0x00, 0x12, 0x00, 0x00, 0x00, 0xff | diff --git a/features/open_flow/nicira_resubmit.feature b/features/open_flow/nicira_resubmit.feature index cd586d94..b25983f7 100644 --- a/features/open_flow/nicira_resubmit.feature +++ b/features/open_flow/nicira_resubmit.feature @@ -1,29 +1,11 @@ -Feature: Pio::NiciraResubmit +@open_flow13 +Feature: NiciraResubmit Scenario: new(1) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::NiciraResubmit.new(1) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | - | action_type.to_hex | 0xffff | - | action_length | 16 | - | vendor.to_hex | 0x2320 | - | subtype | 1 | | in_port | 1 | - - Scenario: new(:in_port) - When I try to create an OpenFlow action with: - """ - Pio::NiciraResubmit.new(:in_port) - """ - Then it should finish successfully - And the action has the following fields and values: - | field | value | - | action_type.to_hex | 0xffff | - | action_length | 16 | - | vendor.to_hex | 0x2320 | - | subtype | 1 | - | in_port | :in_port | diff --git a/features/open_flow/nicira_resubmit_table.feature b/features/open_flow/nicira_resubmit_table.feature index d593f6a0..f9bdc3f5 100644 --- a/features/open_flow/nicira_resubmit_table.feature +++ b/features/open_flow/nicira_resubmit_table.feature @@ -1,31 +1,21 @@ -Feature: Pio::NiciraResubmitTable +Feature: NiciraResubmitTable Scenario: new(in_port: 1, table: 1) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::NiciraResubmitTable.new(in_port: 1, table: 1) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | - | action_type.to_hex | 0xffff | - | action_length | 16 | - | vendor.to_hex | 0x2320 | - | subtype | 14 | | in_port | 1 | | table | 1 | - Scenario: new(:in_port) - When I try to create an OpenFlow action with: + Scenario: new(in_port: 1) + When I create an OpenFlow action with: """ Pio::NiciraResubmitTable.new(in_port: 1) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | - | action_type.to_hex | 0xffff | - | action_length | 16 | - | vendor.to_hex | 0x2320 | - | subtype | 14 | | in_port | 1 | | table.to_hex | 0xff | diff --git a/features/open_flow10/aggregate_stats_reply.feature b/features/open_flow10/aggregate_stats_reply.feature index 82435046..bd112e92 100644 --- a/features/open_flow10/aggregate_stats_reply.feature +++ b/features/open_flow10/aggregate_stats_reply.feature @@ -1,13 +1,11 @@ @open_flow10 -Feature: Pio::AggregateStats::Reply +Feature: AggregateStats::Reply + Scenario: read - When I try to parse a file named "open_flow10/aggregate_stats_reply.raw" with "AggregateStats::Reply" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow10/aggregate_stats_reply.raw" with "AggregateStats::Reply" class + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 17 | - | message_length | 36 | + | version | 1 | | transaction_id | 15 | | xid | 15 | | stats_type | :aggregate | diff --git a/features/open_flow10/aggregate_stats_request.feature b/features/open_flow10/aggregate_stats_request.feature index cc0071a0..0829604f 100644 --- a/features/open_flow10/aggregate_stats_request.feature +++ b/features/open_flow10/aggregate_stats_request.feature @@ -1,16 +1,17 @@ @open_flow10 -Feature: Pio::AggregateStats::Request +Feature: AggregateStats::Request + + Aggregate information about multiple flows is requested with the + Aggregate Stats Request message + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ - Pio::AggregateStats::Request.new(match: Match.new(in_port: 1)) + Pio::AggregateStats::Request.new(match: Pio::Match.new(in_port: 1)) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 56 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | stats_type | :aggregate | @@ -32,13 +33,10 @@ Feature: Pio::AggregateStats::Request Scenario: read - When I try to parse a file named "open_flow10/aggregate_stats_request.raw" with "AggregateStats::Request" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow10/aggregate_stats_request.raw" with "Pio::AggregateStats::Request" class + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 56 | + | version | 1 | | transaction_id | 14 | | xid | 14 | | stats_type | :aggregate | diff --git a/features/open_flow10/bad_request.feature b/features/open_flow10/bad_request.feature index 9c7e61f1..dc7f5b10 100644 --- a/features/open_flow10/bad_request.feature +++ b/features/open_flow10/bad_request.feature @@ -1,33 +1,16 @@ @open_flow10 -Feature: Pio::Error::BadRequest +Feature: Error::BadRequest - Request was not understood error. + Request was not understood error - Scenario: new (raw_data = Echo request 1.3) - When I try to create an OpenFlow message with: + Scenario: new (error_code: :bad_version, raw_data: EchoRequest 1.3) + When I create an OpenFlow message with: """ - Pio::Error::BadRequest.new(raw_data: Pio::OpenFlow13::Echo::Request.new.to_binary) + Pio::Error::BadRequest.new(error_code: :bad_version, + raw_data: Pio::OpenFlow13::Echo::Request.new.to_binary) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 1 | - | message_length | 20 | - | transaction_id | 0 | - | xid | 0 | - | error_type | :bad_request | - | error_code | :bad_version | - | raw_data.length | 8 | - - Scenario: read - When I try to parse a file named "open_flow10/bad_request.raw" with "Pio::Error::BadRequest" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 1 | - | message_length | 20 | | transaction_id | 0 | | xid | 0 | | error_type | :bad_request | diff --git a/features/open_flow10/barrier_reply.feature b/features/open_flow10/barrier_reply.feature index 7859ed1b..ba74a240 100644 --- a/features/open_flow10/barrier_reply.feature +++ b/features/open_flow10/barrier_reply.feature @@ -1,43 +1,32 @@ @open_flow10 -Feature: Pio::Barrier::Reply +Feature: Barrier::Reply + + When the switch received a Barrier Request message, the switch must + finish processing all previously-received messages before executing + any messages beyond the Barrier Request. When such processing is + complete, the switch must send an Barrier Reply message with the xid + of the original request. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Barrier::Reply.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 19 | - | message_length | 8 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | body | | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Barrier::Reply.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 19 | - | message_length | 8 | + | version | 1 | | transaction_id | 123 | | xid | 123 | | body | | - - Scenario: read - When I try to parse a file named "open_flow10/barrier_reply.raw" with "Barrier::Reply" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 19 | - | message_length | 8 | - | transaction_id | 0 | - | xid | 0 | - | body | | diff --git a/features/open_flow10/barrier_request.feature b/features/open_flow10/barrier_request.feature index ffbef949..fe93a8db 100644 --- a/features/open_flow10/barrier_request.feature +++ b/features/open_flow10/barrier_request.feature @@ -1,43 +1,30 @@ @open_flow10 -Feature: Pio::Barrier::Request +Feature: Barrier::Request + + When the controller wants to ensure message dependencies have been + met or wants to receive notifications for completed operations, it + may use an Barrier Request message. This message has no body. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Barrier::Request.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 18 | - | message_length | 8 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | body | | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Barrier::Request.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 18 | - | message_length | 8 | + | version | 1 | | transaction_id | 123 | | xid | 123 | | body | | - - Scenario: read - When I try to parse a file named "open_flow10/barrier_request.raw" with "Barrier::Request" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 18 | - | message_length | 8 | - | transaction_id | 0 | - | xid | 0 | - | body | | diff --git a/features/open_flow10/description_stats_reply.feature b/features/open_flow10/description_stats_reply.feature index 5d9ba2fb..e01b8c0e 100644 --- a/features/open_flow10/description_stats_reply.feature +++ b/features/open_flow10/description_stats_reply.feature @@ -1,13 +1,15 @@ @open_flow10 -Feature: Pio::DescriptionStats::Reply +Feature: DescriptionStats::Reply + + Information about the switch manufacturer, hardware revision, + software revision, serial number, and a description field is + available from a Description Stats Reply. + Scenario: read - When I try to parse a file named "open_flow10/description_stats_reply.raw" with "DescriptionStats::Reply" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow10/description_stats_reply.raw" with "DescriptionStats::Reply" class + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 17 | - | message_length | 1068 | + | version | 1 | | transaction_id | 12 | | xid | 12 | | stats_type | :description | diff --git a/features/open_flow10/description_stats_request.feature b/features/open_flow10/description_stats_request.feature index 899df29b..c355bef1 100644 --- a/features/open_flow10/description_stats_request.feature +++ b/features/open_flow10/description_stats_request.feature @@ -1,43 +1,30 @@ @open_flow10 -Feature: Pio::DescriptionStats::Request +Feature: DescriptionStats::Request + + Information about the switch manufacturer, hardware revision, + software revision, serial number, and a description field is + available by sending a Description Stats Request. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::DescriptionStats::Request.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 12 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | stats_type | :description | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::DescriptionStats::Request.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 12 | + | version | 1 | | transaction_id | 123 | | xid | 123 | | stats_type | :description | - - Scenario: read - When I try to parse a file named "open_flow10/description_stats_request.raw" with "DescriptionStats::Request" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 12 | - | transaction_id | 12 | - | xid | 12 | - | stats_type | :description | diff --git a/features/open_flow10/echo_reply.feature b/features/open_flow10/echo_reply.feature index e3537264..6a000b7e 100644 --- a/features/open_flow10/echo_reply.feature +++ b/features/open_flow10/echo_reply.feature @@ -1,62 +1,41 @@ @open_flow10 -Feature: Pio::Echo::Reply +Feature: Echo::Reply + + An Echo Reply message consists of an OpenFlow header plus the + unmodified data field of an echo request message. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Reply.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 3 | - | message_length | 8 | | transaction_id | 0 | | xid | 0 | | body | | | user_data | | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Reply.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 3 | - | message_length | 8 | | transaction_id | 123 | | xid | 123 | | body | | | user_data | | Scenario: new(body: 'echo reply body') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Reply.new(body: 'echo reply body') """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 3 | - | message_length | 23 | - | transaction_id | 0 | - | xid | 0 | + Then the message has the following fields and values: + | field | value | + | transaction_id | 0 | + | xid | 0 | | body | echo reply body | | user_data | echo reply body | - - Scenario: read (no message body) - When I try to parse a file named "open_flow10/echo_reply.raw" with "Pio::Echo::Reply" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 3 | - | message_length | 8 | - | transaction_id | 6 | - | xid | 6 | - | body | | - | user_data | | diff --git a/features/open_flow10/echo_request.feature b/features/open_flow10/echo_request.feature index 54ec3d43..ba8d20ba 100644 --- a/features/open_flow10/echo_request.feature +++ b/features/open_flow10/echo_request.feature @@ -1,66 +1,43 @@ @open_flow10 -Feature: Pio::Echo::Request +Feature: Echo::Request + + An Echo Request message consists of an OpenFlow header plus an + arbitrary-length data field. The data field might be a message + timestamp to check latency, various lengths to measure bandwidth, or + zero-size to verify liveness between the switch and controller. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Request.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 2 | - | message_length | 8 | | transaction_id | 0 | | xid | 0 | | body | | | user_data | | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Request.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 2 | - | message_length | 8 | | transaction_id | 123 | | xid | 123 | | body | | | user_data | | Scenario: new(body: 'echo request body') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Request.new(body: 'echo request body') """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 2 | - | message_length | 25 | - | transaction_id | 0 | - | xid | 0 | + Then the message has the following fields and values: + | field | value | + | transaction_id | 0 | + | xid | 0 | | body | echo request body | | user_data | echo request body | - - Scenario: read (no message body) - When I try to parse a file named "open_flow10/echo_request.raw" with "Pio::Echo::Request" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 2 | - | message_length | 8 | - | transaction_id | 0 | - | xid | 0 | - | body | | - | user_data | | - - Scenario: parse error - When I try to parse a file named "open_flow10/features_request.raw" with "Pio::Echo::Request" class - Then it should fail with "Pio::ParseError", "Invalid OpenFlow10 Echo Request message." diff --git a/features/open_flow10/enqueue.feature b/features/open_flow10/enqueue.feature index 9bfab741..e58db865 100644 --- a/features/open_flow10/enqueue.feature +++ b/features/open_flow10/enqueue.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::Enqueue +Feature: Enqueue Scenario: new(port: 1, queue_id: 2) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::Enqueue.new(port: 1, queue_id: 2) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 11 | | action_length | 16 | diff --git a/features/open_flow10/exact_match.feature b/features/open_flow10/exact_match.feature index 54c360a1..8e222a8e 100644 --- a/features/open_flow10/exact_match.feature +++ b/features/open_flow10/exact_match.feature @@ -1,19 +1,19 @@ -Feature: Pio::ExactMatch +Feature: ExactMatch Scenario: new (from ARP request Packet In) - When I create an exact match from "open_flow10/packet_in_arp_request.raw" + When I create an exact match from "open_flow10/packet_in_arp_request.rb" Then the message has the following fields and values: | field | value | | wildcards | {} | | in_port | 1 | - | source_mac_address | ac:5d:10:31:37:79 | + | source_mac_address | fa:ce:b0:00:00:cc | | destination_mac_address | ff:ff:ff:ff:ff:ff | | vlan_vid | 65535 | | vlan_priority | 0 | | ether_type | 2054 | | tos | 0 | | ip_protocol | 1 | - | source_ip_address | 192.168.2.254 | - | destination_ip_address | 192.168.2.5 | + | source_ip_address | 192.168.0.1 | + | destination_ip_address | 192.168.0.2 | | transport_source_port | 0 | | transport_destination_port | 0 | diff --git a/features/open_flow10/features_reply.feature b/features/open_flow10/features_reply.feature index f07e68bd..f959b034 100644 --- a/features/open_flow10/features_reply.feature +++ b/features/open_flow10/features_reply.feature @@ -1,7 +1,7 @@ @open_flow10 -Feature: Pio::Features::Reply +Feature: Features::Reply Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Features::Reply.new( datapath_id: 0x123, @@ -9,80 +9,35 @@ Feature: Pio::Features::Reply n_tables: 0xfe, capabilities: [:flow_stats, :table_stats, :port_stats, :queue_stats, :arp_match_ip], actions: [:output, :set_source_mac_address, :set_destination_mac_address], - ports: [{ port_no: 1, - hardware_address: '11:22:33:44:55:66', + ports: [{ number: 1, + mac_address: '11:22:33:44:55:66', name: 'port123', config: [:port_down], state: [:link_down], curr: [:port_10gb_fd, :port_copper] }] ) """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 6 | - | message_length | 80 | - | transaction_id | 0 | - | xid | 0 | - | datapath_id | 291 | - | dpid | 291 | - | n_buffers | 256 | - | n_tables | 254 | - | capabilities | [:flow_stats, :table_stats, :port_stats, :queue_stats, :arp_match_ip] | - | actions | [:output, :set_source_mac_address, :set_destination_mac_address] | - | ports.length | 1 | - | ports[0].datapath_id | 291 | - | ports[0].port_no | 1 | - | ports[0].mac_address | 11:22:33:44:55:66 | - | ports[0].hardware_address | 11:22:33:44:55:66 | - | ports[0].name | port123 | - | ports[0].config | [:port_down] | - | ports[0].state | [:link_down] | - | ports[0].curr | [:port_10gb_fd, :port_copper] | - | ports[0].advertised | [] | - | ports[0].supported | [] | - | ports[0].peer | [] | - - Scenario: read - When I try to parse a file named "open_flow10/features_reply.raw" with "Features::Reply" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 6 | - | message_length | 176 | - | transaction_id | 2 | - | xid | 2 | - | datapath_id | 1 | - | dpid | 1 | - | n_buffers | 256 | - | n_tables | 1 | - | capabilities | [:flow_stats, :table_stats, :port_stats, :arp_match_ip] | - | actions | [:output, :set_vlan_vid, :set_vlan_pcp, :strip_vlan, :set_source_mac_address, :set_destination_mac_address, :set_source_ip_address, :set_destination_ip_address, :set_tos, :set_transport_source_port, :set_transport_destination_port, :enqueue] | - | ports.length | 3 | - | ports[0].datapath_id | 1 | - | ports[0].port_no | 2 | - | ports[0].mac_address | 16:7d:a4:37:ba:10 | - | ports[0].hardware_address | 16:7d:a4:37:ba:10 | - | ports[0].name | trema0-0 | - | ports[0].config | [] | - | ports[0].state | [] | - | ports[0].curr | [:port_10gb_fd, :port_copper] | - | ports[0].advertised | [] | - | ports[0].supported | [] | - | ports[0].peer | [] | - | ports[2].port_no | 1 | - | ports[2].number | 1 | - | ports[2].mac_address | 62:94:3a:f6:40:db | - | ports[2].hardware_address | 62:94:3a:f6:40:db | - | ports[2].name | trema1-0 | - | ports[2].config | [] | - | ports[2].state | [] | - | ports[2].curr | [:port_10gb_fd, :port_copper] | - | ports[2].advertised | [] | - | ports[2].supported | [] | - | ports[2].peer | [] | - | ports[2].up? | true | - | ports[2].down? | false | - | ports[2].local? | false | + Then the message has the following fields and values: + | field | value | + | transaction_id | 0 | + | xid | 0 | + | datapath_id | 291 | + | dpid | 291 | + | n_buffers | 256 | + | n_tables | 254 | + | capabilities | [:flow_stats, :table_stats, :port_stats, :queue_stats, :arp_match_ip] | + | actions | [:output, :set_source_mac_address, :set_destination_mac_address] | + | ports.length | 1 | + | ports[0].datapath_id | 291 | + | ports[0].dpid | 291 | + | ports[0].number | 1 | + | ports[0].local? | false | + | ports[0].mac_address | 11:22:33:44:55:66 | + | ports[0].name | port123 | + | ports[0].config | [:port_down] | + | ports[0].state | [:link_down] | + | ports[0].down? | true | + | ports[0].curr | [:port_10gb_fd, :port_copper] | + | ports[0].advertised | [] | + | ports[0].supported | [] | + | ports[0].peer | [] | diff --git a/features/open_flow10/features_request.feature b/features/open_flow10/features_request.feature index 06f23826..b2424b17 100644 --- a/features/open_flow10/features_request.feature +++ b/features/open_flow10/features_request.feature @@ -1,43 +1,25 @@ @open_flow10 -Feature: Pio::Features::Request +Feature: Features::Request + + Upon OpenFlow channel establishment, the controller sends a + Features::Request message. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Features::Request.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 5 | - | message_length | 8 | | transaction_id | 0 | | xid | 0 | - | user_data | | - + Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Features::Request.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 5 | - | message_length | 8 | | transaction_id | 123 | | xid | 123 | - | user_data | | - - Scenario: read - When I try to parse a file named "open_flow10/features_request.raw" with "Pio::Features::Request" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 5 | - | message_length | 8 | - | transaction_id | 2 | - | xid | 2 | - | user_data | | diff --git a/features/open_flow10/flow_mod.feature b/features/open_flow10/flow_mod.feature index 47486ba3..316ba0e7 100644 --- a/features/open_flow10/flow_mod.feature +++ b/features/open_flow10/flow_mod.feature @@ -1,7 +1,7 @@ @open_flow10 -Feature: Pio::FlowMod +Feature: FlowMod Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::FlowMod.new( actions: [], @@ -10,16 +10,13 @@ Feature: Pio::FlowMod flags: [], hard_timeout: 0, idle_timeout: 0, - match: Match.new(), + match: Pio::Match.new(), out_port: 0, priority: 0 ) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 14 | | actions | [] | | buffer_id | 0 | | command | :add | @@ -41,184 +38,3 @@ Feature: Pio::FlowMod | match.wildcards.key?(:tos) | true | | out_port | 0 | | priority | 0 | - - Scenario: read (Flow Mod Add) - When I try to parse a file named "open_flow10/flow_mod_add.raw" with "Pio::FlowMod" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 14 | - | message_length | 192 | - | transaction_id | 0 | - | xid | 0 | - | match.wildcards | {:source_ip_address=>24, :destination_ip_address=>24} | - | match.in_port | 1 | - | match.source_mac_address | 00:00:00:00:00:0a | - | match.destination_mac_address | 00:00:00:00:00:14 | - | match.vlan_vid | 0 | - | match.vlan_priority | 0 | - | match.ether_type | 2048 | - | match.tos | 0 | - | match.ip_protocol | 1 | - | match.source_ip_address | 10.0.0.0 | - | match.source_ip_address.prefixlen | 8 | - | match.destination_ip_address | 20.0.0.0 | - | match.destination_ip_address.prefixlen | 8 | - | match.transport_source_port | 8 | - | match.transport_destination_port | 0 | - | cookie | 0 | - | command | :add | - | idle_timeout | 0 | - | hard_timeout | 0 | - | priority | 65535 | - | buffer_id | 4294967295 | - | out_port | 65535 | - | flags | [:send_flow_rem] | - | actions.length | 12 | - | actions.first.class | Pio::OpenFlow10::SetVlanVid | - | actions.first.vlan_id | 10 | - - Scenario: read (Flow Mod Modify) - When I try to parse a file named "open_flow10/flow_mod_modify.raw" with "Pio::FlowMod" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 14 | - | message_length | 192 | - | transaction_id | 0 | - | xid | 0 | - | match.wildcards | {:source_ip_address=>24, :destination_ip_address=>24} | - | match.in_port | 1 | - | match.source_mac_address | 00:00:00:00:00:0a | - | match.destination_mac_address | 00:00:00:00:00:14 | - | match.vlan_vid | 0 | - | match.vlan_priority | 0 | - | match.ether_type | 2048 | - | match.tos | 0 | - | match.ip_protocol | 1 | - | match.source_ip_address | 10.0.0.0 | - | match.source_ip_address.prefixlen | 8 | - | match.destination_ip_address | 20.0.0.0 | - | match.destination_ip_address.prefixlen | 8 | - | match.transport_source_port | 8 | - | match.transport_destination_port | 0 | - | cookie | 0 | - | command | :modify | - | idle_timeout | 0 | - | hard_timeout | 0 | - | priority | 65535 | - | buffer_id | 4294967295 | - | out_port | 65535 | - | flags | [:send_flow_rem] | - | actions.length | 12 | - | actions.first.class | Pio::OpenFlow10::SetVlanVid | - | actions.first.vlan_id | 10 | - - Scenario: read (Flow Mod Modify Strict) - When I try to parse a file named "open_flow10/flow_mod_modify_strict.raw" with "Pio::FlowMod" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 14 | - | message_length | 192 | - | transaction_id | 0 | - | xid | 0 | - | match.wildcards | {:source_ip_address=>24, :destination_ip_address=>24} | - | match.in_port | 1 | - | match.source_mac_address | 00:00:00:00:00:0a | - | match.destination_mac_address | 00:00:00:00:00:14 | - | match.vlan_vid | 0 | - | match.vlan_priority | 0 | - | match.ether_type | 2048 | - | match.tos | 0 | - | match.ip_protocol | 1 | - | match.source_ip_address | 10.0.0.0 | - | match.source_ip_address.prefixlen | 8 | - | match.destination_ip_address | 20.0.0.0 | - | match.destination_ip_address.prefixlen | 8 | - | match.transport_source_port | 8 | - | match.transport_destination_port | 0 | - | cookie | 0 | - | command | :modify_strict | - | idle_timeout | 0 | - | hard_timeout | 0 | - | priority | 65535 | - | buffer_id | 4294967295 | - | out_port | 65535 | - | flags | [:send_flow_rem] | - | actions.length | 12 | - | actions.first.class | Pio::OpenFlow10::SetVlanVid | - | actions.first.vlan_id | 10 | - - Scenario: read (Flow Mod Delete) - When I try to parse a file named "open_flow10/flow_mod_delete.raw" with "Pio::FlowMod" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 14 | - | message_length | 72 | - | transaction_id | 0 | - | xid | 0 | - | match.wildcards | {:source_ip_address=>24, :destination_ip_address=>24} | - | match.in_port | 1 | - | match.source_mac_address | 00:00:00:00:00:0a | - | match.destination_mac_address | 00:00:00:00:00:00 | - | match.vlan_vid | 0 | - | match.vlan_priority | 0 | - | match.ether_type | 2048 | - | match.tos | 0 | - | match.ip_protocol | 1 | - | match.source_ip_address | 10.0.0.0 | - | match.source_ip_address.prefixlen | 8 | - | match.destination_ip_address | 20.0.0.0 | - | match.destination_ip_address.prefixlen | 8 | - | match.transport_source_port | 8 | - | match.transport_destination_port | 0 | - | cookie | 0 | - | command | :delete | - | idle_timeout | 0 | - | hard_timeout | 0 | - | priority | 65535 | - | buffer_id | 4294967295 | - | out_port | 65535 | - | flags | [] | - | actions | [] | - - Scenario: read (Flow Mod Delete Strict) - When I try to parse a file named "open_flow10/flow_mod_delete_strict.raw" with "Pio::FlowMod" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 14 | - | message_length | 72 | - | transaction_id | 0 | - | xid | 0 | - | match.wildcards | {:source_ip_address=>24, :destination_ip_address=>24} | - | match.in_port | 1 | - | match.source_mac_address | 00:00:00:00:00:0a | - | match.destination_mac_address | 00:00:00:00:00:14 | - | match.vlan_vid | 0 | - | match.vlan_priority | 0 | - | match.ether_type | 2048 | - | match.tos | 0 | - | match.ip_protocol | 1 | - | match.source_ip_address | 10.0.0.0 | - | match.source_ip_address.prefixlen | 8 | - | match.destination_ip_address | 20.0.0.0 | - | match.destination_ip_address.prefixlen | 8 | - | match.transport_source_port | 8 | - | match.transport_destination_port | 0 | - | cookie | 1 | - | command | :delete_strict | - | idle_timeout | 0 | - | hard_timeout | 0 | - | priority | 65535 | - | buffer_id | 4294967295 | - | out_port | 65535 | - | flags | [] | - | actions | [] | diff --git a/features/open_flow10/flow_removed.feature b/features/open_flow10/flow_removed.feature index 0e234302..55b2d5c1 100644 --- a/features/open_flow10/flow_removed.feature +++ b/features/open_flow10/flow_removed.feature @@ -1,34 +1,32 @@ @open_flow10 -Feature: Pio::FlowRemoved +Feature: FlowRemoved + + If the controller has requested to be notified when flows time out, the datapath + does this with the FlowRemoved message. - @open_flow10 Scenario: read - When I try to parse a file named "open_flow10/flow_removed.raw" with "FlowRemoved" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 11 | - | message_length | 88 | - | transaction_id | 0 | - | xid | 0 | - | match.wildcards.keys.size | 11 | - | match.wildcards.fetch(:destination_mac_address) | true | - | match.wildcards.fetch(:source_mac_address) | true | - | match.wildcards.fetch(:ether_type) | true | - | match.wildcards.fetch(:destination_ip_address_all) | true | - | match.wildcards.fetch(:ip_protocol) | true | - | match.wildcards.fetch(:source_ip_address_all) | true | - | match.wildcards.fetch(:tos) | true | - | match.wildcards.fetch(:transport_destination_port) | true | - | match.wildcards.fetch(:transport_source_port) | true | - | match.wildcards.fetch(:vlan_priority) | true | - | match.wildcards.fetch(:vlan_vid) | true | - | cookie | 1 | - | priority | 65535 | + When I parse a file named "open_flow10/flow_removed.raw" with "FlowRemoved" class + Then the message has the following fields and values: + | field | value | + | transaction_id | 0 | + | xid | 0 | + | match.wildcards.keys.size | 11 | + | match.wildcards.fetch(:destination_mac_address) | true | + | match.wildcards.fetch(:source_mac_address) | true | + | match.wildcards.fetch(:ether_type) | true | + | match.wildcards.fetch(:destination_ip_address_all) | true | + | match.wildcards.fetch(:ip_protocol) | true | + | match.wildcards.fetch(:source_ip_address_all) | true | + | match.wildcards.fetch(:tos) | true | + | match.wildcards.fetch(:transport_destination_port) | true | + | match.wildcards.fetch(:transport_source_port) | true | + | match.wildcards.fetch(:vlan_priority) | true | + | match.wildcards.fetch(:vlan_vid) | true | + | cookie | 1 | + | priority | 65535 | | reason | :delete | - | duration_sec | 0 | - | duration_nsec | 0 | - | idle_timeout | 0 | - | packet_count | 0 | - | byte_count | 0 | + | duration_sec | 0 | + | duration_nsec | 0 | + | idle_timeout | 0 | + | packet_count | 0 | + | byte_count | 0 | diff --git a/features/open_flow10/flow_stats_reply.feature b/features/open_flow10/flow_stats_reply.feature index c4700a3b..f7d1079f 100644 --- a/features/open_flow10/flow_stats_reply.feature +++ b/features/open_flow10/flow_stats_reply.feature @@ -1,16 +1,13 @@ @open_flow10 -Feature: Pio::FlowStats::Reply +Feature: FlowStats::Reply Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::FlowStats::Reply.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 17 | - | message_length | 12 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | stats_type | :flow | @@ -18,88 +15,7 @@ Feature: Pio::FlowStats::Reply @wip Scenario: new(more options) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::FlowStats::Reply.new(more options) """ - Then it should finish successfully - - Scenario: read - When I try to parse a file named "open_flow10/flow_stats_reply.raw" with "FlowStats::Reply" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 17 | - | message_length | 228 | - | transaction_id | 6 | - | xid | 6 | - | stats_type | :flow | - | stats.size | 2 | - | stats[0].entry_length | 104 | - | stats[0].table_id | 3 | - | stats[0].match.wildcards.keys.size | 14 | - | stats[0].match.wildcards.fetch(:destination_mac_address) | true | - | stats[0].match.wildcards.fetch(:source_mac_address) | true | - | stats[0].match.wildcards.fetch(:ether_type) | true | - | stats[0].match.wildcards.fetch(:in_port) | true | - | stats[0].match.wildcards.fetch(:destination_ip_address) | 31 | - | stats[0].match.wildcards.fetch(:destination_ip_address_all) | true | - | stats[0].match.wildcards.fetch(:ip_protocol) | true | - | stats[0].match.wildcards.fetch(:source_ip_address) | 31 | - | stats[0].match.wildcards.fetch(:source_ip_address_all) | true | - | stats[0].match.wildcards.fetch(:tos) | true | - | stats[0].match.wildcards.fetch(:transport_destination_port) | true | - | stats[0].match.wildcards.fetch(:transport_source_port) | true | - | stats[0].match.wildcards.fetch(:vlan_priority) | true | - | stats[0].match.wildcards.fetch(:vlan_vid) | true | - | stats[0].duration_sec | 1 | - | stats[0].duration_nsec | 2 | - | stats[0].priority | 100 | - | stats[0].idle_timeout | 5 | - | stats[0].hard_timeout | 10 | - | stats[0].cookie.to_hex | 0x123456789abcdef | - | stats[0].packet_count | 10 | - | stats[0].byte_count | 1000 | - | stats[0].actions.length | 2 | - | stats[0].actions[0].class | Pio::OpenFlow10::SendOutPort | - | stats[0].actions[0].port | 1 | - | stats[0].actions[0].max_length | 0 | - | stats[0].actions[1].class | Pio::OpenFlow10::SendOutPort | - | stats[0].actions[1].port | 2 | - | stats[0].actions[1].max_length | 0 | - | stats[1].entry_length | 112 | - | stats[1].table_id | 4 | - | stats[1].match.wildcards.keys.size | 14 | - | stats[1].match.wildcards.fetch(:destination_mac_address) | true | - | stats[1].match.wildcards.fetch(:source_mac_address) | true | - | stats[1].match.wildcards.fetch(:ether_type) | true | - | stats[1].match.wildcards.fetch(:in_port) | true | - | stats[1].match.wildcards.fetch(:destination_ip_address) | 31 | - | stats[1].match.wildcards.fetch(:destination_ip_address_all) | true | - | stats[1].match.wildcards.fetch(:ip_protocol) | true | - | stats[1].match.wildcards.fetch(:source_ip_address) | 31 | - | stats[1].match.wildcards.fetch(:source_ip_address_all) | true | - | stats[1].match.wildcards.fetch(:tos) | true | - | stats[1].match.wildcards.fetch(:transport_destination_port) | true | - | stats[1].match.wildcards.fetch(:transport_source_port) | true | - | stats[1].match.wildcards.fetch(:vlan_priority) | true | - | stats[1].match.wildcards.fetch(:vlan_vid) | true | - | stats[1].duration_sec | 1 | - | stats[1].duration_nsec | 2 | - | stats[1].priority | 100 | - | stats[1].idle_timeout | 5 | - | stats[1].hard_timeout | 10 | - | stats[1].cookie.to_hex | 0x123456789abcdef | - | stats[1].packet_count | 10 | - | stats[1].byte_count | 1000 | - | stats[1].actions.length | 3 | - | stats[1].actions[0].class | Pio::OpenFlow10::SendOutPort | - | stats[1].actions[0].port | 1 | - | stats[1].actions[0].max_length | 0 | - | stats[1].actions[1].class | Pio::OpenFlow10::SendOutPort | - | stats[1].actions[1].port | 2 | - | stats[1].actions[1].max_length | 0 | - | stats[1].actions[2].class | Pio::OpenFlow10::SendOutPort | - | stats[1].actions[2].port | 3 | - | stats[1].actions[2].max_length | 0 | diff --git a/features/open_flow10/flow_stats_request.feature b/features/open_flow10/flow_stats_request.feature index eb817ac7..f268bc32 100644 --- a/features/open_flow10/flow_stats_request.feature +++ b/features/open_flow10/flow_stats_request.feature @@ -1,16 +1,17 @@ @open_flow10 -Feature: Pio::FlowStats::Request +Feature: FlowStats::Request + + Information about individual flows is requested with a Flow Stats + Request message. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::FlowStats::Request.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 56 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | stats_type | :flow | @@ -31,16 +32,13 @@ Feature: Pio::FlowStats::Request | out_port | :none | Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ - Pio::FlowStats::Request.new(match: Match.new(in_port: 1)) + Pio::FlowStats::Request.new(match: Pio::Match.new(in_port: 1)) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 56 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | stats_type | :flow | @@ -59,31 +57,3 @@ Feature: Pio::FlowStats::Request | match.wildcards.fetch(:vlan_vid) | true | | table_id.to_hex | 0xff | | out_port | :none | - - - Scenario: read - When I try to parse a file named "open_flow10/flow_stats_request.raw" with "FlowStats::Request" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 56 | - | transaction_id | 13 | - | xid | 13 | - | stats_type | :flow | - | match.wildcards.keys.size | 12 | - | match.wildcards.fetch(:destination_mac_address) | true | - | match.wildcards.fetch(:source_mac_address) | true | - | match.wildcards.fetch(:ether_type) | true | - | match.wildcards.fetch(:in_port) | true | - | match.wildcards.fetch(:destination_ip_address_all) | true | - | match.wildcards.fetch(:ip_protocol) | true | - | match.wildcards.fetch(:source_ip_address_all) | true | - | match.wildcards.fetch(:tos) | true | - | match.wildcards.fetch(:transport_destination_port) | true | - | match.wildcards.fetch(:transport_source_port) | true | - | match.wildcards.fetch(:vlan_priority) | true | - | match.wildcards.fetch(:vlan_vid) | true | - | table_id.to_hex | 0xff | - | out_port | :none | diff --git a/features/open_flow10/hello.feature b/features/open_flow10/hello.feature index 606702e8..509dfc17 100644 --- a/features/open_flow10/hello.feature +++ b/features/open_flow10/hello.feature @@ -1,50 +1,28 @@ @open_flow10 -Feature: Pio::Hello +Feature: Hello Hello messages are exchanged between the switch and controller upon - connection startup. + connection startup. Hello messages have the version field set to the + highest OpenFlow protocol version supported by the sender. Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ - Pio::OpenFlow10::Hello.new + Pio::Hello.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 0 | - | message_length | 8 | + | version | 1 | | transaction_id | 0 | | xid | 0 | - | body | | - | user_data | | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Hello.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 0 | - | message_length | 8 | + | version | 1 | | transaction_id | 123 | | xid | 123 | - | body | | - | user_data | | - - Scenario: read - When I try to parse a file named "open_flow10/hello.raw" with "Hello" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 0 | - | message_length | 8 | - | transaction_id | 23 | - | xid | 23 | - | body | | - | user_data | | diff --git a/features/open_flow10/hello_failed.feature b/features/open_flow10/hello_failed.feature index c70a5195..e1c69256 100644 --- a/features/open_flow10/hello_failed.feature +++ b/features/open_flow10/hello_failed.feature @@ -1,69 +1,19 @@ @open_flow10 -Feature: Pio::Error::HelloFailed +Feature: Error::HelloFailed - Hello protocol failed + Hello protocol failed error - Scenario: new - When I try to create an OpenFlow message with: + Scenario: new(error_code: :incompatible, description: 'error description') + When I create an OpenFlow message with: """ - Pio::Error::HelloFailed.new + Pio::Error::HelloFailed.new(error_code: :incompatible, + description: 'error description') """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 1 | - | message_length | 12 | - | transaction_id | 0 | - | xid | 0 | - | error_type | :hello_failed | - | error_code | :incompatible | - | description | | - - Scenario: new(description: 'error description') - When I try to create an OpenFlow message with: - """ - Pio::Error::HelloFailed.new(description: 'error description') - """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 1 | - | message_length | 29 | | transaction_id | 0 | | xid | 0 | | error_type | :hello_failed | | error_code | :incompatible | | description | error description | - Scenario: new(error_code: :permissions_error) - When I try to create an OpenFlow message with: - """ - Pio::Error::HelloFailed.new(error_code: :permissions_error) - """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 1 | - | message_length | 12 | - | transaction_id | 0 | - | xid | 0 | - | error_type | :hello_failed | - | error_code | :permissions_error | - | description | | - - Scenario: read - When I try to parse a file named "open_flow10/hello_failed.raw" with "Pio::Error::HelloFailed" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 1 | - | message_length | 29 | - | transaction_id | 0 | - | xid | 0 | - | error_type | :hello_failed | - | error_code | :incompatible | - | description | error description | diff --git a/features/open_flow10/packet_in.feature b/features/open_flow10/packet_in.feature index 9936882a..00a42567 100644 --- a/features/open_flow10/packet_in.feature +++ b/features/open_flow10/packet_in.feature @@ -1,57 +1,138 @@ @open_flow10 -Feature: Pio::PacketIn +Feature: PacketIn + + When packets are received by the datapath and sent to the + controller, they use the PacketIn message. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: + """ruby + arp_request = Pio::Arp::Request.new( + source_mac: 'fa:ce:b0:00:00:cc', + sender_protocol_address: '192.168.0.1', + target_protocol_address: '192.168.0.2' + ) + + Pio::PacketIn.new(transaction_id: 0, + buffer_id: 0xffffff00, + in_port: 1, + reason: :no_match, + raw_data: arp_request.to_binary) + """ + Then the message has the following fields and values: + | field | value | + | transaction_id | 0 | + | xid | 0 | + | buffer_id | 4294967040 | + | total_length | 64 | + | in_port | 1 | + | reason | :no_match | + | data.class | Pio::Arp::Request | + | source_mac | fa:ce:b0:00:00:cc | + | destination_mac | ff:ff:ff:ff:ff:ff | + + Scenario: read an empty PacketIn message + Given I use the fixture "open_flow10" + When I create a packet with: + """ruby + Pio::PacketIn.read(eval(IO.read('packet_in.rb'))) + """ + Then the packet has the following fields and values: + | field | value | + | transaction_id | 0 | + | xid | 0 | + | buffer_id | 4294967040 | + | in_port | 1 | + | reason | :no_match | + + Scenario: read a PacketIn message (ARP request) + Given I use the fixture "open_flow10" + When I create a packet with: + """ruby + Pio::PacketIn.read(eval(IO.read('packet_in_arp_request.rb'))) + """ + Then the packet has the following fields and values: + | field | value | + | transaction_id | 0 | + | xid | 0 | + | buffer_id | 4294967040 | + | total_length | 64 | + | in_port | 1 | + | reason | :no_match | + | data.class | Pio::Arp::Request | + | source_mac | fa:ce:b0:00:00:cc | + | destination_mac | ff:ff:ff:ff:ff:ff | + + Scenario: convert PacketIn to Ruby code + When I eval the following Ruby code: + """ruby + arp_request = Pio::Arp::Request.new( + source_mac: 'fa:ce:b0:00:00:cc', + sender_protocol_address: '192.168.0.1', + target_protocol_address: '192.168.0.2' + ) + + Pio::PacketIn.new(transaction_id: 0, + buffer_id: 0xffffff00, + in_port: 1, + reason: :no_match, + raw_data: arp_request.to_binary).to_ruby + """ + Then the result of eval should be: + """ruby + [ + 0x01, # version + 0x0a, # type + 0x00, 0x52, # _length + 0x00, 0x00, 0x00, 0x00, # transaction_id + 0xff, 0xff, 0xff, 0x00, # buffer_id + 0x00, 0x40, # total_length + 0x00, 0x01, # in_port + 0x00, # reason + 0x00, # padding + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xce, 0xb0, 0x00, 0x00, 0xcc, 0x08, 0x06, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0xfa, 0xce, 0xb0, 0x00, 0x00, 0xcc, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # raw_data + ].pack('C82') """ - data_dump = [ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xac, 0x5d, 0x10, 0x31, 0x37, - 0x79, 0x08, 0x06, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, - 0xac, 0x5d, 0x10, 0x31, 0x37, 0x79, 0xc0, 0xa8, 0x02, 0xfe, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa8, 0x02, 0x05, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00 - ].pack('C*') + + Scenario: PacketIn inspection (empty PacketIn) + When I eval the following Ruby code: + """ruby + Pio::PacketIn.new(transaction_id: 0, + buffer_id: 0xffffff00, + in_port: 1, + reason: :no_match).inspect + """ + Then the result of eval should be: + """ + # + """ + + Scenario: PacketIn inspection (ARP request) + When I eval the following Ruby code: + """ruby + arp_request = Pio::Arp::Request.new( + source_mac: 'fa:ce:b0:00:00:cc', + sender_protocol_address: '192.168.0.1', + target_protocol_address: '192.168.0.2' + ) Pio::PacketIn.new(transaction_id: 0, buffer_id: 0xffffff00, in_port: 1, reason: :no_match, - raw_data: data_dump) - """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 10 | - | message_length | 78 | - | transaction_id | 0 | - | xid | 0 | - | buffer_id | 4294967040 | - | total_len | 60 | - | in_port | 1 | - | reason | :no_match | - | raw_data.length | 60 | - | source_mac | ac:5d:10:31:37:79 | - | source_mac.class | Pio::Mac | - | destination_mac | ff:ff:ff:ff:ff:ff | - | destination_mac.class | Pio::Mac | - - Scenario: read - When I try to parse a file named "open_flow10/packet_in_arp_request.raw" with "PacketIn" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 10 | - | message_length | 78 | - | transaction_id | 0 | - | xid | 0 | - | buffer_id | 4294967040 | - | total_len | 60 | - | in_port | 1 | - | reason | :no_match | - | raw_data.length | 60 | - | source_mac | ac:5d:10:31:37:79 | - | source_mac.class | Pio::Mac | - | destination_mac | ff:ff:ff:ff:ff:ff | - | destination_mac.class | Pio::Mac | + raw_data: arp_request.to_binary).inspect + """ + Then the result of eval should be: + """ + #> + """ + + Scenario: PacketIn class inspection + When I eval the following Ruby code: + """ruby + Pio::PacketIn.inspect + """ + Then the result of eval should be: + """ + PacketIn(open_flow_version: uint8, message_type: uint8, message_length: uint16, transaction_id: uint32, buffer_id: uint32, total_length: uint16, in_port: uint16, reason: symbol, raw_data: string) + """ diff --git a/features/open_flow10/packet_in.raw b/features/open_flow10/packet_in.raw deleted file mode 100644 index 417c0329..00000000 Binary files a/features/open_flow10/packet_in.raw and /dev/null differ diff --git a/features/open_flow10/packet_in_arp_request.raw b/features/open_flow10/packet_in_arp_request.raw deleted file mode 100644 index 93b4edd9..00000000 Binary files a/features/open_flow10/packet_in_arp_request.raw and /dev/null differ diff --git a/features/open_flow10/packet_out.feature b/features/open_flow10/packet_out.feature index 59381d98..59487a98 100644 --- a/features/open_flow10/packet_out.feature +++ b/features/open_flow10/packet_out.feature @@ -1,19 +1,30 @@ @open_flow10 -Feature: Pio::PacketOut - Scenario: read - When I try to parse a file named "open_flow10/packet_out.raw" with "PacketOut" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 13 | - | message_length | 88 | - | transaction_id | 22 | - | xid | 22 | - | buffer_id | 4294967295 | - | in_port | 65535 | - | actions.length | 1 | - | actions.first.class | Pio::OpenFlow10::SendOutPort | - | actions.first.port | 2 | - | actions.first.max_length | 65535 | - | raw_data.length | 64 | +Feature: PacketOut + Scenario: new + When I create an OpenFlow message with: + """ + arp_request = Pio::Arp::Request.new( + source_mac: 'fa:ce:b0:00:00:cc', + sender_protocol_address: '192.168.0.1', + target_protocol_address: '192.168.0.2' + ) + + Pio::PacketOut.new( + transaction_id: 0x16, + buffer_id: 0xffffffff, + in_port: 0xffff, + actions: Pio::SendOutPort.new(2), + raw_data: arp_request.to_binary + ) + """ + Then the message has the following fields and values: + | field | value | + | transaction_id | 22 | + | xid | 22 | + | buffer_id.to_hex | 0xffffffff | + | in_port.to_hex | 0xffff | + | actions.length | 1 | + | actions[0].class | Pio::OpenFlow10::SendOutPort | + | actions[0].port | 2 | + | actions[0].max_length | 65535 | + | raw_data.length | 64 | diff --git a/features/open_flow10/port_stats_request.feature b/features/open_flow10/port_stats_request.feature index 57b45396..df1a1aa8 100644 --- a/features/open_flow10/port_stats_request.feature +++ b/features/open_flow10/port_stats_request.feature @@ -1,47 +1,26 @@ -Feature: Pio::OpenFlow10::PortStats::Request - @open_flow10 +@open_flow10 +Feature: PortStats::Request Scenario: new(:none) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::OpenFlow10::PortStats::Request.new(:none) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 20 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | stats_type | :port | | port | :none | - @open_flow10 Scenario: new(:none, transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::OpenFlow10::PortStats::Request.new(:none, transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 20 | - | transaction_id | 123 | - | xid | 123 | - | stats_type | :port | - | port | :none | - - @open_flow10 - Scenario: read - When I try to parse a file named "open_flow10/port_stats_request.raw" with "PortStats::Request" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 20 | + | version | 1 | | transaction_id | 123 | | xid | 123 | | stats_type | :port | diff --git a/features/open_flow10/port_status.feature b/features/open_flow10/port_status.feature index ecbc6882..71ec99a2 100644 --- a/features/open_flow10/port_status.feature +++ b/features/open_flow10/port_status.feature @@ -1,23 +1,24 @@ -Feature: Pio::PortStatus +@open_flow10 +Feature: PortStatus + + As physical ports are added, modified, and removed from the + datapath, the controller needs to be informed with the PortStatus + message. + Scenario: read - When I try to parse a file named "open_flow10/port_status.raw" with "PortStatus" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | class | Pio::OpenFlow10::PortStatus | - | ofp_version | 1 | - | message_type | 12 | - | message_length | 64 | - | transaction_id | 4 | - | xid | 4 | - | reason | :delete | - | desc.port_no | 65533 | - | desc.hardware_address | 01:02:03:04:05:06 | - | desc.name | foo | - | desc.config | [:no_flood] | - | desc.state | [:stp_forward, :stp_block] | - | desc.curr | [:port_10mb_hd] | - | desc.advertised | [:port_1gb_fd] | - | desc.supported | [:port_autoneg] | - | desc.peer | [:port_pause_asym] | + When I parse a file named "open_flow10/port_status.raw" with "PortStatus" class + Then the message has the following fields and values: + | field | value | + | transaction_id | 4 | + | xid | 4 | + | reason | :delete | + | number | 65533 | + | mac_address | 01:02:03:04:05:06 | + | name | foo | + | config | [:no_flood] | + | state | [:stp_forward, :stp_block] | + | curr | [:port_10mb_hd] | + | advertised | [:port_1gb_fd] | + | supported | [:port_autoneg] | + | peer | [:port_pause_asym] | diff --git a/features/open_flow10/queue_stats_request.feature b/features/open_flow10/queue_stats_request.feature index a8ddec9a..d4c402e0 100644 --- a/features/open_flow10/queue_stats_request.feature +++ b/features/open_flow10/queue_stats_request.feature @@ -1,51 +1,33 @@ -Feature: Pio::OpenFlow10::QueueStats::Request - @open_flow10 +@open_flow10 +Feature: QueueStats::Request + + Information about queues is requested with a Queue Stats Request + message. + Scenario: new(port: 1, queue_id: 1) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::OpenFlow10::QueueStats::Request.new(port: 1, queue_id: 1) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 20 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | stats_type | :queue | | port | 1 | | queue_id | 1 | - @open_flow10 Scenario: new(port: 1, queue_id: 1, transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::OpenFlow10::QueueStats::Request.new(port: 1, queue_id: 1, transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 20 | + | version | 1 | | transaction_id | 123 | | xid | 123 | | stats_type | :queue | | port | 1 | | queue_id | 1 | - - @open_flow10 - Scenario: read - When I try to parse a file named "open_flow10/queue_stats_request.raw" with "QueueStats::Request" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 20 | - | transaction_id | 123 | - | xid | 123 | - | stats_type | :queue | - | port | :all | - | queue_id | 1 | diff --git a/features/open_flow10/send_out_port.feature b/features/open_flow10/send_out_port.feature index ecc8f738..5c72341c 100644 --- a/features/open_flow10/send_out_port.feature +++ b/features/open_flow10/send_out_port.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::SendOutPort +Feature: SendOutPort Scenario: new(1) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(1) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 8 | @@ -15,12 +14,11 @@ Feature: Pio::SendOutPort | max_length | 65535 | Scenario: new(:all) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:all) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 8 | @@ -28,12 +26,11 @@ Feature: Pio::SendOutPort | max_length | 65535 | Scenario: new(:controller) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:controller) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 8 | @@ -41,12 +38,11 @@ Feature: Pio::SendOutPort | max_length | 65535 | Scenario: new(:local) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:local) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 8 | @@ -54,12 +50,11 @@ Feature: Pio::SendOutPort | max_length | 65535 | Scenario: new(:table) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:table) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 8 | @@ -67,12 +62,11 @@ Feature: Pio::SendOutPort | max_length | 65535 | Scenario: new(:in_port) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:in_port) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 8 | @@ -80,12 +74,11 @@ Feature: Pio::SendOutPort | max_length | 65535 | Scenario: new(:normal) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:normal) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 8 | @@ -93,12 +86,11 @@ Feature: Pio::SendOutPort | max_length | 65535 | Scenario: new(:flood) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:flood) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 8 | diff --git a/features/open_flow10/set_destination_mac_address.feature b/features/open_flow10/set_destination_mac_address.feature index 88787e8e..656b93cf 100644 --- a/features/open_flow10/set_destination_mac_address.feature +++ b/features/open_flow10/set_destination_mac_address.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::SetDestinationMacAddress +Feature: SetDestinationMacAddress Scenario: new('11:22:33:44:55:66') - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SetDestinationMacAddress.new('11:22:33:44:55:66') """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 5 | | action_length | 16 | diff --git a/features/open_flow10/set_ip_destination_address.feature b/features/open_flow10/set_ip_destination_address.feature index 63246ad7..dbffa517 100644 --- a/features/open_flow10/set_ip_destination_address.feature +++ b/features/open_flow10/set_ip_destination_address.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::SetDestinationIpAddress +Feature: SetDestinationIpAddress Scenario: new('192.168.0.1') - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::SetDestinationIpAddress.new('192.168.0.1') """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 7 | | action_length | 8 | diff --git a/features/open_flow10/set_source_ip_address.feature b/features/open_flow10/set_source_ip_address.feature index 4d8fb805..57d0f2c8 100644 --- a/features/open_flow10/set_source_ip_address.feature +++ b/features/open_flow10/set_source_ip_address.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::SetSourceIpAddress +Feature: SetSourceIpAddress Scenario: new('192.168.0.1') - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::SetSourceIpAddress.new('192.168.0.1') """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 6 | | action_length | 8 | diff --git a/features/open_flow10/set_source_mac_address.feature b/features/open_flow10/set_source_mac_address.feature index 77690030..dbf3835e 100644 --- a/features/open_flow10/set_source_mac_address.feature +++ b/features/open_flow10/set_source_mac_address.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::SetSourceMacAddress +Feature: SetSourceMacAddress Scenario: new('11:22:33:44:55:66') - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SetSourceMacAddress.new('11:22:33:44:55:66') """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 4 | | action_length | 16 | diff --git a/features/open_flow10/set_tos.feature b/features/open_flow10/set_tos.feature index b2738109..9f3b3d7e 100644 --- a/features/open_flow10/set_tos.feature +++ b/features/open_flow10/set_tos.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::SetTos +Feature: SetTos Scenario: new(0b11111100) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::SetTos.new(0b11111100) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 8 | | action_length | 8 | diff --git a/features/open_flow10/set_transport_destination_port.feature b/features/open_flow10/set_transport_destination_port.feature index 9013fc09..c802019c 100644 --- a/features/open_flow10/set_transport_destination_port.feature +++ b/features/open_flow10/set_transport_destination_port.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::SetTransportDestinationPort +Feature: SetTransportDestinationPort Scenario: new(100) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::SetTransportDestinationPort.new(100) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 10 | | action_length | 8 | diff --git a/features/open_flow10/set_transport_source_port.feature b/features/open_flow10/set_transport_source_port.feature index 6567620e..4f089566 100644 --- a/features/open_flow10/set_transport_source_port.feature +++ b/features/open_flow10/set_transport_source_port.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::SetTransportSourcePort +Feature: SetTransportSourcePort Scenario: new(100) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::SetTransportSourcePort.new(100) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 9 | | action_length | 8 | diff --git a/features/open_flow10/set_vlan_priority.feature b/features/open_flow10/set_vlan_priority.feature index 15ea17f5..aa3c3ade 100644 --- a/features/open_flow10/set_vlan_priority.feature +++ b/features/open_flow10/set_vlan_priority.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::SetVlanPriority +Feature: SetVlanPriority Scenario: new(1) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::SetVlanPriority.new(1) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 2 | | action_length | 8 | diff --git a/features/open_flow10/set_vlan_vid.feature b/features/open_flow10/set_vlan_vid.feature index 4018e37f..5b07f380 100644 --- a/features/open_flow10/set_vlan_vid.feature +++ b/features/open_flow10/set_vlan_vid.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::SetVlanVid +Feature: SetVlanVid Scenario: new(1) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::SetVlanVid.new(1) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 1 | | action_length | 8 | diff --git a/features/open_flow10/strip_vlan_header.feature b/features/open_flow10/strip_vlan_header.feature index 0dff0d88..7bf02a08 100644 --- a/features/open_flow10/strip_vlan_header.feature +++ b/features/open_flow10/strip_vlan_header.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::OpenFlow10::StripVlanHeader +Feature: StripVlanHeader Scenario: new - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::OpenFlow10::StripVlanHeader.new """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 3 | | action_length | 8 | diff --git a/features/open_flow10/table_stats_request.feature b/features/open_flow10/table_stats_request.feature index d1be8cea..ee032b1b 100644 --- a/features/open_flow10/table_stats_request.feature +++ b/features/open_flow10/table_stats_request.feature @@ -1,31 +1,29 @@ @open_flow10 -Feature: Pio::TableStats::Request +Feature: TableStats::Request + + Information about tables is requested with a Table Stats Request + message. + Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::TableStats::Request.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 12 | + | version | 1 | | transaction_id | 0 | | xid | 0 | | stats_type | :table | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::TableStats::Request.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 1 | - | message_type | 16 | - | message_length | 12 | + | version | 1 | | transaction_id | 123 | | xid | 123 | | stats_type | :table | diff --git a/features/open_flow10/vendor_action.feature b/features/open_flow10/vendor_action.feature index 52624c95..3dff2171 100644 --- a/features/open_flow10/vendor_action.feature +++ b/features/open_flow10/vendor_action.feature @@ -1,13 +1,12 @@ @open_flow10 -Feature: Pio::VendorAction +Feature: VendorAction Scenario: new(1) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ - Pio::VendorAction.new(1) + Pio::OpenFlow10::VendorAction.new(1) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type.to_hex | 0xffff | | length | 8 | diff --git a/features/open_flow13/apply_actions.feature b/features/open_flow13/apply_actions.feature index dac26446..d89984f3 100644 --- a/features/open_flow13/apply_actions.feature +++ b/features/open_flow13/apply_actions.feature @@ -1,27 +1,25 @@ @open_flow13 -Feature: Apply-Actions instruction. +Feature: Apply Scenario: new() - When I try to create an OpenFlow instruction with: + When I create an OpenFlow instruction with: """ Pio::Apply.new """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | class | Pio::Apply | - | instruction_type | 4 | - | instruction_length | 8 | - | actions | [] | + Then the message has the following fields and values: + | field | value | + | class | Pio::OpenFlow13::Apply | + | instruction_type | 4 | + | instruction_length | 8 | + | actions | [] | Scenario: new(SendOutPort.new(1)) - When I try to create an OpenFlow instruction with: + When I create an OpenFlow instruction with: """ - Pio::Apply.new(SendOutPort.new(1)) + Pio::Apply.new(Pio::SendOutPort.new(1)) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | class | Pio::Apply | + | class | Pio::OpenFlow13::Apply | | instruction_type | 4 | | instruction_length | 24 | | actions.size | 1 | @@ -29,11 +27,10 @@ Feature: Apply-Actions instruction. | actions.at(0).port | 1 | Scenario: read - When I try to parse a file named "open_flow13/apply_actions.raw" with "Pio::Apply" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/apply_actions.raw" with "Pio::Apply" class + Then the message has the following fields and values: | field | value | - | class | Pio::Apply | + | class | Pio::OpenFlow13::Apply | | instruction_type | 4 | | instruction_length | 24 | | actions.size | 1 | diff --git a/features/open_flow13/bad_request.feature b/features/open_flow13/bad_request.feature index d84f11ef..92417e39 100644 --- a/features/open_flow13/bad_request.feature +++ b/features/open_flow13/bad_request.feature @@ -1,19 +1,16 @@ @open_flow13 -Feature: Pio::Error::BadRequest +Feature: Error::BadRequest Request was not understood error. Scenario: new (raw_data = Echo request 1.0) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Error::BadRequest.new(raw_data: Pio::OpenFlow10::Echo::Request.new.to_binary) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 1 | - | message_length | 20 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | error_type | :bad_request | @@ -21,13 +18,10 @@ Feature: Pio::Error::BadRequest | raw_data.length | 8 | Scenario: read - When I try to parse a file named "open_flow13/bad_request.raw" with "Pio::Error::BadRequest" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/bad_request.raw" with "Pio::Error::BadRequest" class + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 1 | - | message_length | 20 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | error_type | :bad_request | diff --git a/features/open_flow13/copy_ttl_inwards.feature b/features/open_flow13/copy_ttl_inwards.feature new file mode 100644 index 00000000..f25539e8 --- /dev/null +++ b/features/open_flow13/copy_ttl_inwards.feature @@ -0,0 +1,13 @@ +@open_flow13 +Feature: CopyTtlInwards + + Copies TTL "inwards" -- from outermost to next-to-outermost + + Scenario: new + When I create an OpenFlow action with: + """ + Pio::CopyTtlInwards.new + """ + Then the action has the following fields and values: + | field | value | + | action_type | 12 | diff --git a/features/open_flow13/copy_ttl_outwards.feature b/features/open_flow13/copy_ttl_outwards.feature new file mode 100644 index 00000000..eeb7fcd1 --- /dev/null +++ b/features/open_flow13/copy_ttl_outwards.feature @@ -0,0 +1,13 @@ +@open_flow13 +Feature: CopyTtlOutwards + + Copies TTL "outwards" -- from next-to-outermost to outermost + + Scenario: new + When I create an OpenFlow action with: + """ + Pio::CopyTtlOutwards.new + """ + Then the action has the following fields and values: + | field | value | + | action_type | 11 | diff --git a/features/open_flow13/decrement_ip_ttl.feature b/features/open_flow13/decrement_ip_ttl.feature new file mode 100644 index 00000000..c67a916d --- /dev/null +++ b/features/open_flow13/decrement_ip_ttl.feature @@ -0,0 +1,11 @@ +@open_flow13 +Feature: DecrementIpTtl + + Scenario: new + When I create an OpenFlow action with: + """ + Pio::DecrementIpTtl.new + """ + Then the action has the following fields and values: + | field | value | + | action_type | 24 | diff --git a/features/open_flow13/echo_reply.feature b/features/open_flow13/echo_reply.feature index d4585de4..a01f4004 100644 --- a/features/open_flow13/echo_reply.feature +++ b/features/open_flow13/echo_reply.feature @@ -1,86 +1,60 @@ @open_flow13 -Feature: Pio::Echo::Reply +Feature: Echo::Reply Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Reply.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 3 | - | message_length | 8 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | | | user_data | | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Reply.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 3 | - | message_length | 8 | + | version | 4 | | transaction_id | 123 | | xid | 123 | | body | | | user_data | | Scenario: new(body: 'echo reply body') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Reply.new(body: 'echo reply body') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 3 | - | message_length | 23 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | echo reply body | | user_data | echo reply body | - Scenario: new(unknown_attr: 'foo') and error - When I try to create an OpenFlow message with: - """ - Pio::Echo::Reply.new(unknown_attr: 'foo') - """ - Then it should fail with "RuntimeError", "Unknown option: unknown_attr" - Scenario: read (no message body) - When I try to parse a file named "open_flow13/echo_reply_no_body.raw" with "Pio::Echo::Reply" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/echo_reply_no_body.raw" with "Pio::Echo::Reply" class + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 3 | - | message_length | 8 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | | | user_data | | Scenario: read - When I try to parse a file named "open_flow13/echo_reply_body.raw" with "Pio::Echo::Reply" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/echo_reply_body.raw" with "Pio::Echo::Reply" class + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 3 | - | message_length | 28 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | hogehogehogehogehoge | | user_data | hogehogehogehogehoge | - - Scenario: parse error - When I try to parse a file named "open_flow10/features_request.raw" with "Pio::Echo::Reply" class - Then it should fail with "Pio::ParseError", "Invalid OpenFlow13 Echo Reply message." diff --git a/features/open_flow13/echo_request.feature b/features/open_flow13/echo_request.feature index 0b04326f..93e7a3b7 100644 --- a/features/open_flow13/echo_request.feature +++ b/features/open_flow13/echo_request.feature @@ -1,86 +1,60 @@ @open_flow13 -Feature: Pio::Echo::Request +Feature: Echo::Request Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Request.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 2 | - | message_length | 8 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | | | user_data | | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Request.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 2 | - | message_length | 8 | + | version | 4 | | transaction_id | 123 | | xid | 123 | | body | | | user_data | | Scenario: new(body: 'echo request body') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Echo::Request.new(body: 'echo request body') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 2 | - | message_length | 25 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | echo request body | | user_data | echo request body | - Scenario: new(unknown_attr: 'foo') and error - When I try to create an OpenFlow message with: - """ - Pio::Echo::Request.new(unknown_attr: 'foo') - """ - Then it should fail with "RuntimeError", "Unknown option: unknown_attr" - Scenario: read (no message body) - When I try to parse a file named "open_flow13/echo_request_no_body.raw" with "Pio::Echo::Request" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/echo_request_no_body.raw" with "Pio::Echo::Request" class + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 2 | - | message_length | 8 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | | | user_data | | Scenario: read - When I try to parse a file named "open_flow13/echo_request_body.raw" with "Pio::Echo::Request" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/echo_request_body.raw" with "Pio::Echo::Request" class + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 2 | - | message_length | 28 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | hogehogehogehogehoge | | user_data | hogehogehogehogehoge | - - Scenario: parse error - When I try to parse a file named "open_flow10/features_request.raw" with "Pio::Echo::Request" class - Then it should fail with "Pio::ParseError", "Invalid OpenFlow13 Echo Request message." diff --git a/features/open_flow13/features_reply.feature b/features/open_flow13/features_reply.feature index 9ebc7825..cb624576 100644 --- a/features/open_flow13/features_reply.feature +++ b/features/open_flow13/features_reply.feature @@ -1,7 +1,7 @@ @open_flow13 -Feature: Pio::Features::Reply +Feature: Features::Reply Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Features::Reply.new( datapath_id: 0x123, @@ -10,12 +10,9 @@ Feature: Pio::Features::Reply capabilities: [:flow_stats, :table_stats, :port_stats, :group_stats, :ip_reasm, :queue_stats, :port_blocked] ) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 6 | - | message_length | 32 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | datapath_id | 291 | @@ -25,21 +22,3 @@ Feature: Pio::Features::Reply | auxiliary_id | 0 | | capabilities | [:flow_stats, :table_stats, :port_stats, :group_stats, :ip_reasm, :queue_stats, :port_blocked] | | reserved | 0 | - - Scenario: read - When I try to parse a file named "open_flow13/features_reply.raw" with "Pio::Features::Reply" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 6 | - | message_length | 32 | - | transaction_id | 0 | - | xid | 0 | - | datapath_id | 281474976710657 | - | dpid | 281474976710657 | - | n_buffers | 256 | - | n_tables | 1 | - | auxiliary_id | 0 | - | capabilities | [:flow_stats, :table_stats, :port_stats, :group_stats, :ip_reasm, :queue_stats, :port_blocked] | - | reserved | 0 | diff --git a/features/open_flow13/features_request.feature b/features/open_flow13/features_request.feature index 761fed56..b77470f0 100644 --- a/features/open_flow13/features_request.feature +++ b/features/open_flow13/features_request.feature @@ -1,54 +1,25 @@ @open_flow13 -Feature: Pio::Features::Request +Feature: Features::Request Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Features::Request.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 5 | - | message_length | 8 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | body | | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Features::Request.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 5 | - | message_length | 8 | + | version | 4 | | transaction_id | 123 | | xid | 123 | | body | | - - Scenario: new(unknown_attr: 'foo') and error - When I try to create an OpenFlow message with: - """ - Pio::Features::Request.new(unknown_attr: 'foo') - """ - Then it should fail with "RuntimeError", "Unknown option: unknown_attr" - - Scenario: read - When I try to parse a file named "open_flow13/features_request.raw" with "Pio::Features::Request" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 5 | - | message_length | 8 | - | transaction_id | 0 | - | xid | 0 | - | body | | - - Scenario: parse error - When I try to parse a file named "open_flow10/hello.raw" with "Pio::Features::Request" class - Then it should fail with "Pio::ParseError", "Invalid OpenFlow13 Features Request message." diff --git a/features/open_flow13/flow_mod.feature b/features/open_flow13/flow_mod.feature index 6bd03f11..39e437f3 100644 --- a/features/open_flow13/flow_mod.feature +++ b/features/open_flow13/flow_mod.feature @@ -1,16 +1,13 @@ @open_flow13 -Feature: Pio::FlowMod +Feature: FlowMod Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::FlowMod.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 14 | - | message_length | 56 | + | version | 4 | | to_binary.length | 56 | | transaction_id | 0 | | xid | 0 | @@ -28,17 +25,14 @@ Feature: Pio::FlowMod | match.match_fields | [] | | instructions | [] | - Scenario: new(instructions: Pio::Apply.new(SendOutPort.new(1))) - When I try to create an OpenFlow message with: + Scenario: new(instructions: Pio::Apply.new(Pio::SendOutPort.new(1))) + When I create an OpenFlow message with: """ - Pio::FlowMod.new(instructions: Pio::Apply.new(SendOutPort.new(1))) + Pio::FlowMod.new(instructions: Pio::Apply.new(Pio::SendOutPort.new(1))) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 14 | - | message_length | 80 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | cookie | 0 | @@ -54,21 +48,18 @@ Feature: Pio::FlowMod | flags | [] | | match.match_fields | [] | | instructions.size | 1 | - | instructions.at(0).class | Pio::Apply | + | instructions.at(0).class | Pio::OpenFlow13::Apply | | instructions.at(0).actions.at(0).class | Pio::OpenFlow13::SendOutPort | | instructions.at(0).actions.at(0).port | 1 | - Scenario: new(match: Pio::Match.new(in_port: 1), instructions: Pio::Apply.new(SendOutPort.new(1))) - When I try to create an OpenFlow message with: + Scenario: new(match: Pio::Match.new(in_port: 1), instructions: Pio::Apply.new(Pio::SendOutPort.new(1))) + When I create an OpenFlow message with: """ - Pio::FlowMod.new(match: Pio::Match.new(in_port: 1), instructions: Pio::Apply.new(SendOutPort.new(1))) + Pio::FlowMod.new(match: Pio::Match.new(in_port: 1), instructions: Pio::Apply.new(Pio::SendOutPort.new(1))) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 14 | - | message_length | 88 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | cookie | 0 | @@ -84,57 +75,6 @@ Feature: Pio::FlowMod | flags | [] | | match.in_port | 1 | | instructions.size | 1 | - | instructions.at(0).class | Pio::Apply | - | instructions.at(0).actions.at(0).class | Pio::OpenFlow13::SendOutPort | - | instructions.at(0).actions.at(0).port | 1 | - - Scenario: read (no match or instructions) - When I try to parse a file named "open_flow13/flow_mod_no_match_or_instructions.raw" with "Pio::FlowMod" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 14 | - | message_length | 56 | - | transaction_id | 0 | - | xid | 0 | - | cookie | 0 | - | cookie_mask | 0 | - | table_id | 0 | - | command | :add | - | idle_timeout | 0 | - | hard_timeout | 0 | - | priority.to_hex | 0xffff | - | buffer_id | :no_buffer | - | out_port | :any | - | out_group | :any | - | flags | [] | - | match.match_fields | [] | - | instructions | [] | - - Scenario: read (instruction = apply, action = SendOutPort(port: 1)) - When I try to parse a file named "open_flow13/flow_mod_add_apply_no_match.raw" with "Pio::FlowMod" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 14 | - | message_length | 80 | - | transaction_id | 0 | - | xid | 0 | - | cookie | 0 | - | cookie_mask | 0 | - | table_id | 0 | - | command | :add | - | idle_timeout | 0 | - | hard_timeout | 0 | - | priority.to_hex | 0xffff | - | buffer_id | :no_buffer | - | out_port | :any | - | out_group | :any | - | flags | [] | - | match.match_fields | [] | - | instructions.size | 1 | - | instructions.at(0).class | Pio::Apply | + | instructions.at(0).class | Pio::OpenFlow13::Apply | | instructions.at(0).actions.at(0).class | Pio::OpenFlow13::SendOutPort | | instructions.at(0).actions.at(0).port | 1 | diff --git a/features/open_flow13/goto_table.feature b/features/open_flow13/goto_table.feature index 018f2007..2cac27d1 100644 --- a/features/open_flow13/goto_table.feature +++ b/features/open_flow13/goto_table.feature @@ -1,26 +1,24 @@ @open_flow13 -Feature: Pio::GotoTable +Feature: GotoTable Scenario: new(1) - When I try to create an OpenFlow instruction with: + When I create an OpenFlow instruction with: """ - Pio::GotoTable.new(1) + Pio::OpenFlow13::GotoTable.new(1) """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | class | Pio::GotoTable | - | instruction_type | 1 | - | instruction_length | 8 | - | to_binary_s.length | 8 | - | table_id | 1 | + Then the message has the following fields and values: + | field | value | + | class | Pio::OpenFlow13::GotoTable | + | instruction_type | 1 | + | instruction_length | 8 | + | to_binary_s.length | 8 | + | table_id | 1 | Scenario: read - When I try to parse a file named "open_flow13/instruction_goto_table.raw" with "Pio::GotoTable" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | class | Pio::GotoTable | - | instruction_type | 1 | - | instruction_length | 8 | - | to_binary_s.length | 8 | - | table_id | 1 | + When I parse a file named "open_flow13/instruction_goto_table.raw" with "Pio::OpenFlow13::GotoTable" class + Then the message has the following fields and values: + | field | value | + | class | Pio::OpenFlow13::GotoTable | + | instruction_type | 1 | + | instruction_length | 8 | + | to_binary_s.length | 8 | + | table_id | 1 | diff --git a/features/open_flow13/hello.feature b/features/open_flow13/hello.feature index b1dcffae..2b5a9b18 100644 --- a/features/open_flow13/hello.feature +++ b/features/open_flow13/hello.feature @@ -1,55 +1,25 @@ @open_flow13 -Feature: Pio::Hello +Feature: Hello Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Hello.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 0 | - | message_length | 16 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | supported_versions | [:open_flow13] | Scenario: new(transaction_id: 123) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Hello.new(transaction_id: 123) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 0 | - | message_length | 16 | + | version | 4 | | transaction_id | 123 | | xid | 123 | | supported_versions | [:open_flow13] | - - Scenario: read (no version bitmap) - When I try to parse a file named "open_flow13/hello_no_version_bitmap.raw" with "Pio::Hello" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 0 | - | message_length | 8 | - | transaction_id | 0 | - | xid | 0 | - | supported_versions | [] | - - Scenario: read - When I try to parse a file named "open_flow13/hello_version_bitmap.raw" with "Pio::Hello" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 0 | - | message_length | 16 | - | transaction_id | 0 | - | xid | 0 | - | supported_versions | [:open_flow10, :open_flow13] | diff --git a/features/open_flow13/hello_failed.feature b/features/open_flow13/hello_failed.feature index 065deb74..8fb4a9ad 100644 --- a/features/open_flow13/hello_failed.feature +++ b/features/open_flow13/hello_failed.feature @@ -1,19 +1,16 @@ @open_flow13 -Feature: Pio::Error::HelloFailed +Feature: Error::HelloFailed Hello protocol failed Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Error::HelloFailed.new """ - Then it should finish successfully And the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 1 | - | message_length | 12 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | error_type | :hello_failed | @@ -21,16 +18,13 @@ Feature: Pio::Error::HelloFailed | description | | Scenario: new(description: 'error description') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Error::HelloFailed.new(description: 'error description') """ - Then it should finish successfully And the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 1 | - | message_length | 29 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | error_type | :hello_failed | @@ -38,32 +32,15 @@ Feature: Pio::Error::HelloFailed | description | error description | Scenario: new(error_code: :permissions_error) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Error::HelloFailed.new(error_code: :permissions_error) """ - Then it should finish successfully And the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 1 | - | message_length | 12 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | error_type | :hello_failed | | error_code | :permissions_error | | description | | - - Scenario: read - When I try to parse a file named "open_flow13/hello_failed.raw" with "Pio::Error::HelloFailed" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 1 | - | message_length | 29 | - | transaction_id | 0 | - | xid | 0 | - | error_type | :hello_failed | - | error_code | :incompatible | - | description | error description | diff --git a/features/open_flow13/match.feature b/features/open_flow13/match.feature index b6e4de4d..82f097d8 100644 --- a/features/open_flow13/match.feature +++ b/features/open_flow13/match.feature @@ -1,789 +1,780 @@ @open_flow13 -Feature: Pio::Match +Feature: Match Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | match_fields | [] | Scenario: new(in_port: 1) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(in_port: 1) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | in_port | 1 | Scenario: new(metadata: 1) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(metadata: 1) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | metadata | 1 | Scenario: new(metadata: 1, metadata_mask: 1) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(metadata: 1, metadata_mask: 1) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | metadata | 1 | | metadata_mask | 1 | Scenario: new(source_mac_address: '01:02:03:04:05:06') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(source_mac_address: '01:02:03:04:05:06') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | source_mac_address | 01:02:03:04:05:06 | Scenario: new(destination_mac_address: '01:02:03:04:05:06') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(destination_mac_address: '01:02:03:04:05:06') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | destination_mac_address | 01:02:03:04:05:06 | Scenario: new(source_mac_address: '01:02:03:04:05:06', source_mac_address_mask: 'ff:ff:ff:00:00:00') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(source_mac_address: '01:02:03:04:05:06', source_mac_address_mask: 'ff:ff:ff:00:00:00') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | source_mac_address | 01:02:03:04:05:06 | | source_mac_address_mask | ff:ff:ff:00:00:00 | Scenario: new(destination_mac_address: '01:02:03:04:05:06', destination_mac_address_mask: 'ff:ff:ff:00:00:00') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(destination_mac_address: '01:02:03:04:05:06', destination_mac_address_mask: 'ff:ff:ff:00:00:00') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | destination_mac_address | 01:02:03:04:05:06 | | destination_mac_address_mask | ff:ff:ff:00:00:00 | Scenario: new(ether_type: 0x0800) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | Scenario: new(vlan_vid: 10) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(vlan_vid: 10) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | vlan_vid | 10 | Scenario: new(vlan_vid: 10, vlan_pcp: 5) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(vlan_vid: 10, vlan_pcp: 5) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | vlan_vid | 10 | | vlan_pcp | 5 | Scenario: new(eth_type: 2048, ip_dscp: 46) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2048, ip_dscp: 46) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_dscp | 46 | Scenario: new(eth_type: 2048, ip_ecn: 3) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2048, ip_ecn: 46) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_ecn | 3 | Scenario: new(ether_type: 0x0800, ipv4_source_address: '192.168.0.1') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ipv4_source_address: '192.168.0.1') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ipv4_source_address | 192.168.0.1 | Scenario: new(ether_type: 0x0800, ipv4_destination_address: '192.168.0.1') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ipv4_destination_address: '192.168.0.1') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ipv4_destination_address | 192.168.0.1 | Scenario: new(ether_type: 0x0800, ipv4_source_address: '192.168.0.1', ivp4_source_address_mask: '255.255.0.0') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ipv4_source_address: '192.168.0.1', ipv4_source_address_mask: '255.255.0.0') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ipv4_source_address | 192.168.0.1 | | ipv4_source_address_mask | 255.255.0.0 | Scenario: new(ether_type: 0x0800, ipv4_destination_address: '192.168.0.1', ivp4_destination_address_mask: '255.255.0.0') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ipv4_destination_address: '192.168.0.1', ipv4_destination_address_mask: '255.255.0.0') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ipv4_destination_address | 192.168.0.1 | | ipv4_destination_address_mask | 255.255.0.0 | Scenario: new(ether_type: 0x0800, ip_protocol: 6, tcp_source_port: 1111) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ip_protocol: 6, tcp_source_port: 1111) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 6 | | tcp_source_port | 1111 | Scenario: new(ether_type: 0x0800, ip_protocol: 6, tcp_destination_port: 80) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ip_protocol: 6, tcp_destination_port: 80) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 6 | | tcp_destination_port | 80 | Scenario: new(ether_type: 0x0800, ip_protocol: 17, udp_source_port: 2222) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ip_protocol: 17, udp_source_port: 2222) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 17 | | udp_source_port | 2222 | Scenario: new(ether_type: 0x0800, ip_protocol: 17, udp_destination_port: 3333) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ip_protocol: 17, udp_destination_port: 3333) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 17 | | udp_destination_port | 3333 | Scenario: new(ether_type: 0x0800, ip_protocol: 132, sctp_source_port: 22) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ip_protocol: 132, sctp_source_port: 22) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 132 | | sctp_source_port | 22 | Scenario: new(ether_type: 0x0800, ip_protocol: 132, sctp_destination_port: 22) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ip_protocol: 132, sctp_destination_port: 22) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 132 | | sctp_destination_port | 22 | Scenario: new(ether_type: 0x0800, ip_protocol: 1, icmpv4_type: 8) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ip_protocol: 1, icmpv4_type: 8) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 1 | | icmpv4_type | 8 | Scenario: new(ether_type: 0x0800, ip_protocol: 1, icmpv4_code: 0) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x0800, ip_protocol: 1, icmpv4_code: 0) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 1 | | icmpv4_code | 0 | Scenario: new(eth_type: 2054, arp_operation: 1) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_operation: 1) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_operation | 1 | Scenario: new(eth_type: 2054, arp_sender_protocol_address: '1.2.3.4') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_sender_protocol_address: '1.2.3.4') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_sender_protocol_address | 1.2.3.4 | Scenario: new(eth_type: 2054, arp_sender_protocol_address: '1.2.3.4', arp_sender_protocol_address_mask: '255.255.0.0') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_sender_protocol_address: '1.2.3.4', arp_sender_protocol_address_mask: '255.255.0.0') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_sender_protocol_address | 1.2.3.4 | | arp_sender_protocol_address_mask | 255.255.0.0 | Scenario: new(eth_type: 2054, arp_target_protocol_address: '1.2.3.4') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_target_protocol_address: '1.2.3.4') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_target_protocol_address | 1.2.3.4 | Scenario: new(eth_type: 2054, arp_target_protocol_address: '1.2.3.4', arp_target_protocol_address_mask: '255.255.0.0') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_target_protocol_address: '1.2.3.4', arp_target_protocol_address_mask: '255.255.0.0') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_target_protocol_address | 1.2.3.4 | | arp_target_protocol_address_mask | 255.255.0.0 | Scenario: new(eth_type: 2054, arp_sender_hardware_address: '11:22:33:44:55:66') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_sender_hardware_address: '11:22:33:44:55:66') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_sender_hardware_address | 11:22:33:44:55:66 | Scenario: new(eth_type: 2054, arp_sender_hardware_address: '11:22:33:44:55:66', arp_sender_hardware_address_mask: 'ff:ff:ff:00:00:00') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_sender_hardware_address: '11:22:33:44:55:66', arp_sender_hardware_address_mask: 'ff:ff:ff:00:00:00') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_sender_hardware_address | 11:22:33:44:55:66 | | arp_sender_hardware_address_mask | ff:ff:ff:00:00:00 | Scenario: new(eth_type: 2054, arp_target_hardware_address: '11:22:33:44:55:66') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_target_hardware_address: '11:22:33:44:55:66') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_target_hardware_address | 11:22:33:44:55:66 | Scenario: new(eth_type: 2054, arp_target_hardware_address: '11:22:33:44:55:66', arp_target_hardware_address_mask: 'ff:ff:ff:00:00:00') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 2054, arp_target_hardware_address: '11:22:33:44:55:66', arp_target_hardware_address_mask: 'ff:ff:ff:00:00:00') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_target_hardware_address | 11:22:33:44:55:66 | | arp_target_hardware_address_mask | ff:ff:ff:00:00:00 | Scenario: new(ether_type: 0x86dd, ipv6_source_address: '2001:db8:bd05:1d2:288a:1fc0:1:10ee') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x86dd, ipv6_source_address: '2001:db8:bd05:1d2:288a:1fc0:1:10ee') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 34525 | | ipv6_source_address | 2001:db8:bd05:1d2:288a:1fc0:1:10ee | Scenario: new(ether_type: 0x86dd, ipv6_source_address: '2001:db8:bd05:1d2:288a:1fc0:1:10ee', ipv6_source_address_mask: 'ffff:ffff:ffff:ffff::') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x86dd, ipv6_source_address: '2001:db8:bd05:1d2:288a:1fc0:1:10ee', ipv6_source_address_mask: 'ffff:ffff:ffff:ffff::') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 34525 | | ipv6_source_address | 2001:db8:bd05:1d2:288a:1fc0:1:10ee | | ipv6_source_address_mask | ffff:ffff:ffff:ffff:: | Scenario: new(ether_type: 0x86dd, ipv6_destination_address: '2001:db8:bd05:1d2:288a:1fc0:1:10ee') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x86dd, ipv6_destination_address: '2001:db8:bd05:1d2:288a:1fc0:1:10ee') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 34525 | | ipv6_destination_address | 2001:db8:bd05:1d2:288a:1fc0:1:10ee | Scenario: new(ether_type: 0x86dd, ipv6_destination_address: '2001:db8:bd05:1d2:288a:1fc0:1:10ee', ipv6_destination_address_mask: 'ffff:ffff:ffff:ffff::') - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(ether_type: 0x86dd, ipv6_destination_address: '2001:db8:bd05:1d2:288a:1fc0:1:10ee', ipv6_destination_address_mask: 'ffff:ffff:ffff:ffff::') """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | ether_type | 34525 | | ipv6_destination_address | 2001:db8:bd05:1d2:288a:1fc0:1:10ee | | ipv6_destination_address_mask | ffff:ffff:ffff:ffff:: | Scenario: new(tunnel_id: 1) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(tunnel_id: 1) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | tunnel_id | 1 | Scenario: new(tunnel_id: 1, tunnel_id_mask: 9223372036854775808) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::Match.new(tunnel_id: 1, tunnel_id_mask: 9223372036854775808) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | tunnel_id | 1 | | tunnel_id_mask | 9223372036854775808 | + Scenario: new(packet_reg0: 1) + When I create an OpenFlow message with: + """ + Pio::Match.new(packet_reg0: 1) + """ + Then the message has the following fields and values: + | field | value | + | packet_reg0 | 1 | + + Scenario: new(packet_reg0: 1, packet_reg0_mask: 1) + When I create an OpenFlow message with: + """ + Pio::Match.new(packet_reg0: 1, packet_reg0_mask: 1) + """ + Then the message has the following fields and values: + | field | value | + | packet_reg0 | 1 | + | packet_reg0_mask | 1 | + + Scenario: new(packet_reg1: 1) + When I create an OpenFlow message with: + """ + Pio::Match.new(packet_reg1: 1) + """ + Then the message has the following fields and values: + | field | value | + | packet_reg1 | 1 | + + Scenario: new(packet_reg1: 1, packet_reg1_mask: 1) + When I create an OpenFlow message with: + """ + Pio::Match.new(packet_reg1: 1, packet_reg1_mask: 1) + """ + Then the message has the following fields and values: + | field | value | + | packet_reg1 | 1 | + | packet_reg1_mask | 1 | + + Scenario: new(packet_reg2: 1) + When I create an OpenFlow message with: + """ + Pio::Match.new(packet_reg2: 1) + """ + Then the message has the following fields and values: + | field | value | + | packet_reg2 | 1 | + + Scenario: new(packet_reg2: 1, packet_reg2_mask: 1) + When I create an OpenFlow message with: + """ + Pio::Match.new(packet_reg2: 1, packet_reg2_mask: 1) + """ + Then the message has the following fields and values: + | field | value | + | packet_reg2 | 1 | + | packet_reg2_mask | 1 | + + Scenario: new(packet_reg3: 1) + When I create an OpenFlow message with: + """ + Pio::Match.new(packet_reg3: 1) + """ + Then the message has the following fields and values: + | field | value | + | packet_reg3 | 1 | + + Scenario: new(packet_reg3: 1, packet_reg3_mask: 1) + When I create an OpenFlow message with: + """ + Pio::Match.new(packet_reg3: 1, packet_reg3_mask: 1) + """ + Then the message has the following fields and values: + | field | value | + | packet_reg3 | 1 | + | packet_reg3_mask | 1 | + Scenario: read (file: open_flow13/oxm_no_fields.raw) - When I try to parse a file named "open_flow13/oxm_no_fields.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_no_fields.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | match_fields | [] | Scenario: read (file: open_flow13/oxm_in_port_field.raw) - When I try to parse a file named "open_flow13/oxm_in_port_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_in_port_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | in_port | 1 | Scenario: read (file: open_flow13/oxm_metadata_field.raw) - When I try to parse a file named "open_flow13/oxm_metadata_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_metadata_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | metadata | 1 | Scenario: read (file: open_flow13/oxm_metadata_masked_field.raw) - When I try to parse a file named "open_flow13/oxm_metadata_masked_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_metadata_masked_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | metadata | 1 | | metadata_mask | 18446744069414584320 | Scenario: read (file: open_flow13/oxm_ether_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_ether_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ether_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | destination_mac_address | ff:ff:ff:ff:ff:ff | Scenario: read (file: open_flow13/oxm_ether_source_field.raw) - When I try to parse a file named "open_flow13/oxm_ether_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ether_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | source_mac_address | 01:02:03:04:05:06 | Scenario: read (file: open_flow13/oxm_masked_ether_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_ether_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_ether_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | destination_mac_address | ff:ff:ff:ff:ff:ff | | destination_mac_address_mask | ff:ff:ff:00:00:00 | Scenario: read (file: open_flow13/oxm_masked_ether_source_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_ether_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_ether_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | source_mac_address | 01:02:03:04:05:06 | | source_mac_address_mask | ff:ff:ff:00:00:00 | Scenario: read (file: open_flow13/oxm_ether_type_field.raw) - When I try to parse a file named "open_flow13/oxm_ether_type_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ether_type_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 0 | Scenario: read (file: open_flow13/oxm_vlan_vid_field.raw) - When I try to parse a file named "open_flow13/oxm_vlan_vid_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_vlan_vid_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | vlan_vid | 10 | Scenario: read (file: open_flow13/oxm_vlan_pcp_field.raw) - When I try to parse a file named "open_flow13/oxm_vlan_pcp_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_vlan_pcp_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | vlan_vid | 10 | | vlan_pcp | 5 | Scenario: read (file: open_flow13/oxm_ip_dscp_field.raw) - When I try to parse a file named "open_flow13/oxm_ip_dscp_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ip_dscp_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_dscp | 46 | Scenario: read (file: open_flow13/oxm_ip_ecn_field.raw) - When I try to parse a file named "open_flow13/oxm_ip_ecn_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ip_ecn_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_ecn | 3 | Scenario: read (file: open_flow13/oxm_ipv4_source_field.raw) - When I try to parse a file named "open_flow13/oxm_ipv4_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ipv4_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ipv4_source_address | 1.2.3.4 | Scenario: read (file: open_flow13/oxm_ipv4_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_ipv4_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ipv4_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ipv4_destination_address | 11.22.33.44 | Scenario: read (file: open_flow13/oxm_masked_ipv4_source_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_ipv4_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_ipv4_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ipv4_source_address | 1.2.3.4 | | ipv4_source_address_mask | 255.255.0.0 | Scenario: read (file: open_flow13/oxm_masked_ipv4_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_ipv4_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_ipv4_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ipv4_destination_address | 11.22.33.44 | | ipv4_destination_address_mask | 255.255.255.0 | Scenario: read (file: open_flow13/oxm_tcp_source_field.raw) - When I try to parse a file named "open_flow13/oxm_tcp_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_tcp_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 6 | | tcp_source_port | 1111 | Scenario: read (file: open_flow13/oxm_tcp_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_tcp_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_tcp_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 6 | | tcp_destination_port | 80 | Scenario: read (file: open_flow13/oxm_udp_source_field.raw) - When I try to parse a file named "open_flow13/oxm_udp_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_udp_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 17 | | udp_source_port | 2222 | Scenario: read (file: open_flow13/oxm_udp_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_udp_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_udp_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 17 | | udp_destination_port | 3333 | Scenario: read (file: open_flow13/oxm_sctp_source_field.raw) - When I try to parse a file named "open_flow13/oxm_sctp_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_sctp_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 132 | | sctp_source_port | 22 | Scenario: read (file: open_flow13/oxm_sctp_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_sctp_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_sctp_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 132 | | sctp_destination_port | 22 | Scenario: read (file: open_flow13/oxm_icmpv4_type_field.raw) - When I try to parse a file named "open_flow13/oxm_icmpv4_type_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_icmpv4_type_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 1 | | icmpv4_type | 8 | Scenario: read (file: open_flow13/oxm_icmpv4_code_field.raw) - When I try to parse a file named "open_flow13/oxm_icmpv4_code_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_icmpv4_code_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2048 | | ip_protocol | 1 | | icmpv4_code | 0 | Scenario: read (file: open_flow13/oxm_arp_op_field.raw) - When I try to parse a file named "open_flow13/oxm_arp_op_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_arp_op_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_operation | 1 | Scenario: read (file: open_flow13/oxm_arp_spa_field.raw) - When I try to parse a file named "open_flow13/oxm_arp_spa_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_arp_spa_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_sender_protocol_address | 1.2.3.4 | Scenario: read (file: open_flow13/oxm_masked_arp_spa_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_arp_spa_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_arp_spa_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_sender_protocol_address | 1.2.3.4 | | arp_sender_protocol_address_mask | 255.255.0.0 | Scenario: read (file: open_flow13/oxm_arp_tpa_field.raw) - When I try to parse a file named "open_flow13/oxm_arp_tpa_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_arp_tpa_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_target_protocol_address | 1.2.3.4 | Scenario: read (file: open_flow13/oxm_masked_arp_tpa_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_arp_tpa_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_arp_tpa_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_target_protocol_address | 1.2.3.4 | | arp_target_protocol_address_mask | 255.255.0.0 | Scenario: read (file: open_flow13/oxm_arp_sha_field.raw) - When I try to parse a file named "open_flow13/oxm_arp_sha_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_arp_sha_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_sender_hardware_address | 11:22:33:44:55:66 | Scenario: read (file: open_flow13/oxm_masked_arp_sha_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_arp_sha_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_arp_sha_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_sender_hardware_address | 11:22:33:44:55:66 | | arp_sender_hardware_address_mask | ff:ff:ff:ff:ff:ff | Scenario: read (file: open_flow13/oxm_arp_tha_field.raw) - When I try to parse a file named "open_flow13/oxm_arp_tha_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_arp_tha_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_target_hardware_address | 11:22:33:44:55:66 | Scenario: read (file: open_flow13/oxm_masked_arp_tha_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_arp_tha_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_arp_tha_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 2054 | | arp_target_hardware_address | 11:22:33:44:55:66 | | arp_target_hardware_address_mask | ff:ff:ff:ff:ff:ff | Scenario: read (file: open_flow13/oxm_ipv6_source_field.raw) - When I try to parse a file named "open_flow13/oxm_ipv6_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ipv6_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 34525 | | ipv6_source_address | 2001:db8:bd05:1d2:288a:1fc0:1:10ee | Scenario: read (file: open_flow13/oxm_masked_ipv6_source_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_ipv6_source_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_ipv6_source_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 34525 | | ipv6_source_address | 2001:db8:bd05:1d2:288a:1fc0:1:10ee | | ipv6_source_address_mask | ffff:ffff:ffff:ffff:: | Scenario: read (file: open_flow13/oxm_ipv6_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_ipv6_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_ipv6_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 34525 | | ipv6_destination_address | 2001:db8:bd05:1d2:288a:1fc0:1:10ee | Scenario: read (file: open_flow13/oxm_masked_ipv6_destination_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_ipv6_destination_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_ipv6_destination_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | ether_type | 34525 | | ipv6_destination_address | 2001:db8:bd05:1d2:288a:1fc0:1:10ee | | ipv6_destination_address_mask | ffff:ffff:ffff:ffff:: | Scenario: read (file: open_flow13/oxm_tunnel_id_field.raw) - When I try to parse a file named "open_flow13/oxm_tunnel_id_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_tunnel_id_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | tunnel_id | 1 | Scenario: read (file: open_flow13/oxm_masked_tunnel_id_field.raw) - When I try to parse a file named "open_flow13/oxm_masked_tunnel_id_field.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_masked_tunnel_id_field.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | tunnel_id | 1 | | tunnel_id_mask | 9223372036854775808 | - Scenario: read (file: open_flow13/oxm_invalid_field.raw) - When I try to parse a file named "open_flow13/oxm_invalid_field.raw" with "Pio::Match" class - Then it should fail with "RuntimeError", "Unknown OXM field value: 40" - Scenario: read (file: open_flow13/oxm_experimenter_stratos_basic_dot11.raw) - When I try to parse a file named "open_flow13/oxm_experimenter_stratos_basic_dot11.raw" with "Pio::Match" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/oxm_experimenter_stratos_basic_dot11.raw" with "Pio::Match" class + Then the message has the following fields and values: | field | value | | match_fields.at(0).oxm_field | 0 | | match_fields.at(0).experimenter | 4278247501 | diff --git a/features/open_flow13/meter.feature b/features/open_flow13/meter.feature index 870d7066..2f6dc15c 100644 --- a/features/open_flow13/meter.feature +++ b/features/open_flow13/meter.feature @@ -1,26 +1,24 @@ @open_flow13 -Feature: Pio::Meter +Feature: Meter Scenario: new(1) - When I try to create an OpenFlow instruction with: + When I create an OpenFlow instruction with: """ - Pio::Meter.new(1) + Pio::OpenFlow13::Meter.new(1) """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | class | Pio::Meter | - | instruction_type | 6 | - | instruction_length | 8 | - | to_binary_s.length | 8 | - | meter_id | 1 | + Then the message has the following fields and values: + | field | value | + | class | Pio::OpenFlow13::Meter | + | instruction_type | 6 | + | instruction_length | 8 | + | to_binary_s.length | 8 | + | meter_id | 1 | Scenario: read - When I try to parse a file named "open_flow13/instruction_meter.raw" with "Pio::Meter" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | class | Pio::Meter | - | instruction_type | 6 | - | instruction_length | 8 | - | to_binary_s.length | 8 | - | meter_id | 1 | + When I parse a file named "open_flow13/instruction_meter.raw" with "Pio::OpenFlow13::Meter" class + Then the message has the following fields and values: + | field | value | + | class | Pio::OpenFlow13::Meter | + | instruction_type | 6 | + | instruction_length | 8 | + | to_binary_s.length | 8 | + | meter_id | 1 | diff --git a/features/open_flow13/nicira_conjunction.feature b/features/open_flow13/nicira_conjunction.feature new file mode 100644 index 00000000..4faacee7 --- /dev/null +++ b/features/open_flow13/nicira_conjunction.feature @@ -0,0 +1,15 @@ +@open_flow13 +Feature: NiciraConjunction + + Provides conjunctive flow match. + + Scenario: new(clause: 1, n_clauses: 2, conjunction_id: 1) + When I create an OpenFlow action with: + """ + Pio::NiciraConjunction.new(clause: 1, n_clauses: 2, conjunction_id: 1) + """ + Then the action has the following fields and values: + | field | value | + | clause | 1 | + | n_clauses | 2 | + | conjunction_id | 1 | diff --git a/features/open_flow13/nicira_reg_load.feature b/features/open_flow13/nicira_reg_load.feature index 8c3629fc..d168e5c9 100644 --- a/features/open_flow13/nicira_reg_load.feature +++ b/features/open_flow13/nicira_reg_load.feature @@ -1,56 +1,41 @@ @open_flow13 -Feature: Pio::NiciraRegLoad +Feature: NiciraRegLoad + + Copies value[0:n_bits] to destination[ofs:ofs+n_bits], where a[b:c] + denotes the bits within 'a' numbered 'b' through 'c' (not including bit 'c'). Scenario: new(0xdeadbeef, :reg0) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::NiciraRegLoad.new(0xdeadbeef, :reg0) """ - Then it should finish successfully - And the action has the following fields and values: - | field | value | - | action_type.to_hex | 0xffff | - | action_length | 24 | - | experimenter_id.to_hex | 0x2320 | - | experimenter_type | 7 | - | offset | 0 | - | n_bits | 32 | - | destination | :reg0 | - | destination_internal | 65540 | - | value.to_hex | 0xdeadbeef | + Then the action has the following fields and values: + | field | value | + | offset | 0 | + | n_bits | 32 | + | destination | :reg0 | + | value.to_hex | 0xdeadbeef | Scenario: new(0xdeadbeef, :metadata) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::NiciraRegLoad.new(0xdeadbeef, :metadata) """ - Then it should finish successfully - And the action has the following fields and values: - | field | value | - | action_type.to_hex | 0xffff | - | action_length | 24 | - | experimenter_id.to_hex | 0x2320 | - | experimenter_type | 7 | - | offset | 0 | - | n_bits | 64 | - | destination | :metadata | - | destination_internal | 2147484680 | - | value.to_hex | 0xdeadbeef | + Then the action has the following fields and values: + | field | value | + | offset | 0 | + | n_bits | 64 | + | destination | :metadata | + | value.to_hex | 0xdeadbeef | - Scenario: new(0xdeadbeef, :metadata, offset: 32, n_bits: 32) - When I try to create an OpenFlow action with: + Scenario: new(0xdeadbeef, :reg0, offset: 16, n_bits: 16) + When I create an OpenFlow action with: """ - Pio::NiciraRegLoad.new(0xdeadbeef, :metadata, offset: 32, n_bits: 32) + Pio::NiciraRegLoad.new(0xdeadbeef, :reg0, offset: 16, n_bits: 16) """ - Then it should finish successfully - And the action has the following fields and values: - | field | value | - | action_type.to_hex | 0xffff | - | action_length | 24 | - | experimenter_id.to_hex | 0x2320 | - | experimenter_type | 7 | - | offset | 32 | - | n_bits | 32 | - | destination | :metadata | - | destination_internal | 2147484680 | - | value.to_hex | 0xdeadbeef | + Then the action has the following fields and values: + | field | value | + | offset | 16 | + | n_bits | 16 | + | destination | :reg0 | + | value.to_hex | 0xdeadbeef | diff --git a/features/open_flow13/nicira_reg_move.feature b/features/open_flow13/nicira_reg_move.feature index 490e846d..6f31a447 100644 --- a/features/open_flow13/nicira_reg_move.feature +++ b/features/open_flow13/nicira_reg_move.feature @@ -1,42 +1,48 @@ @open_flow13 -Feature: Pio::NiciraRegMove +Feature: NiciraRegMove - Scenario: new(from: :arp_sender_hardware_address, to: :arp_target_hardware_address) - When I try to create an OpenFlow action with: + Copies source[source_offset:sourcce_offset+n_bits] to + destination[destination_offset:dst_ofs+n_bits], where a[b:c] denotes + the bits within 'a' numbered 'b' through 'c' (not including bit 'c'). + + Scenario: new(source: :arp_sender_hardware_address, destination: :arp_target_hardware_address) + When I create an OpenFlow action with: """ - Pio::NiciraRegMove.new(from: :arp_sender_hardware_address, to: :arp_target_hardware_address) + Pio::NiciraRegMove.new(source: :arp_sender_hardware_address, + destination: :arp_target_hardware_address) """ - Then it should finish successfully - And the action has the following fields and values: - | field | value | - | action_type.to_hex | 0xffff | - | action_length | 24 | - | experimenter_id.to_hex | 0x2320 | - | experimenter_type | 6 | - | from | :arp_sender_hardware_address | - | source_oxm_field | 24 | - | source_oxm_length | 6 | - | to | :arp_target_hardware_address | - | destination_oxm_field | 25 | - | destination_oxm_length | 6 | + Then the action has the following fields and values: + | field | value | + | n_bits | 48 | + | source | :arp_sender_hardware_address | + | source_offset | 0 | + | destination | :arp_target_hardware_address | + | destination_offset | 0 | - Scenario: new(from: :reg0, to: :reg7) - When I try to create an OpenFlow action with: + Scenario: new(source: :reg0, destination: :reg7) + When I create an OpenFlow action with: + """ + Pio::NiciraRegMove.new(source: :reg0, destination: :reg7) + """ + Then the action has the following fields and values: + | field | value | + | n_bits | 32 | + | source | :reg0 | + | source_offset | 0 | + | destination | :reg7 | + | destination_offset | 0 | + + Scenario: new(source: :reg0, source_offset: 16, destination: :reg7, destination_offset: 16, n_bits: 16) + When I create an OpenFlow action with: """ - Pio::NiciraRegMove.new(from: :reg0, to: :reg7) + Pio::NiciraRegMove.new(source: :reg0, source_offset: 16, + destination: :reg7, destination_offset: 16, + n_bits: 16) """ - Then it should finish successfully - And the action has the following fields and values: - | field | value | - | action_type.to_hex | 0xffff | - | action_length | 24 | - | experimenter_id.to_hex | 0x2320 | - | experimenter_type | 6 | - | from | :reg0 | - | source_oxm_class | 1 | - | source_oxm_field | 0 | - | source_oxm_length | 4 | - | to | :reg7 | - | destination_oxm_class | 1 | - | destination_oxm_field | 7 | - | destination_oxm_length | 4 | + Then the action has the following fields and values: + | field | value | + | n_bits | 16 | + | source | :reg0 | + | source_offset | 16 | + | destination | :reg7 | + | destination_offset | 16 | diff --git a/features/open_flow13/nicira_send_out_port.feature b/features/open_flow13/nicira_send_out_port.feature index 8ddf50c8..0e84c38a 100644 --- a/features/open_flow13/nicira_send_out_port.feature +++ b/features/open_flow13/nicira_send_out_port.feature @@ -1,20 +1,28 @@ @open_flow13 -Feature: Pio::NiciraSendOutPort +Feature: NiciraSendOutPort + + Outputs to the OpenFlow port number written to source[offset:offset+n_bits] Scenario: new(:reg0) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::NiciraSendOutPort.new(:reg0) """ - Then it should finish successfully - And the action has the following fields and values: - | field | value | - | action_type.to_hex | 0xffff | - | action_length | 24 | - | experimenter_id.to_hex | 0x2320 | - | experimenter_type | 15 | - | offset | 0 | - | source | :reg0 | - | max_length | 0 | + Then the action has the following fields and values: + | field | value | + | offset | 0 | + | n_bits | 32 | + | source | :reg0 | + | max_length.to_hex | 0xffff | - + Scenario: new(:reg0, offset: 16, n_bits: 16, max_length: 256) + When I create an OpenFlow action with: + """ + Pio::NiciraSendOutPort.new(:reg0, offset: 16, n_bits: 16, max_length: 256) + """ + Then the action has the following fields and values: + | field | value | + | offset | 16 | + | n_bits | 16 | + | source | :reg0 | + | max_length | 256 | diff --git a/features/open_flow13/nicira_stack_pop.feature b/features/open_flow13/nicira_stack_pop.feature new file mode 100644 index 00000000..8c86c79c --- /dev/null +++ b/features/open_flow13/nicira_stack_pop.feature @@ -0,0 +1,26 @@ +@open_flow13 +Feature: NiciraStackPop + + Pops field[offset: offset + n_bits] from top of the stack. + + Scenario: new(:reg0) + When I create an OpenFlow action with: + """ + Pio::NiciraStackPop.new(:reg0) + """ + Then the action has the following fields and values: + | field | value | + | offset | 0 | + | n_bits | 32 | + | field | :reg0 | + + Scenario: new(:reg0, n_bits: 16, offset: 16) + When I create an OpenFlow action with: + """ + Pio::NiciraStackPop.new(:reg0, n_bits: 16, offset: 16) + """ + Then the action has the following fields and values: + | field | value | + | offset | 16 | + | n_bits | 16 | + | field | :reg0 | diff --git a/features/open_flow13/nicira_stack_push.feature b/features/open_flow13/nicira_stack_push.feature new file mode 100644 index 00000000..97888bfa --- /dev/null +++ b/features/open_flow13/nicira_stack_push.feature @@ -0,0 +1,26 @@ +@open_flow13 +Feature: NiciraStackPush + + Pushes field[offset: offset + n_bits] to top of the stack. + + Scenario: new(:reg0) + When I create an OpenFlow action with: + """ + Pio::NiciraStackPush.new(:reg0) + """ + Then the action has the following fields and values: + | field | value | + | offset | 0 | + | n_bits | 32 | + | field | :reg0 | + + Scenario: new(:reg0, n_bits: 16, offset: 16) + When I create an OpenFlow action with: + """ + Pio::NiciraStackPush.new(:reg0, n_bits: 16, offset: 16) + """ + Then the action has the following fields and values: + | field | value | + | offset | 16 | + | n_bits | 16 | + | field | :reg0 | diff --git a/features/open_flow13/packet_in.feature b/features/open_flow13/packet_in.feature index 74f00e07..0add0887 100644 --- a/features/open_flow13/packet_in.feature +++ b/features/open_flow13/packet_in.feature @@ -1,16 +1,13 @@ @open_flow13 -Feature: Pio::PacketIn +Feature: PacketIn Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::PacketIn.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 10 | - | message_length | 34 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | buffer_id | 0 | @@ -22,7 +19,7 @@ Feature: Pio::PacketIn | raw_data.length | 0 | Scenario: new (raw_data = ARP request) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ data_dump = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xac, 0x5d, 0x10, 0x31, 0x37, @@ -35,11 +32,9 @@ Feature: Pio::PacketIn Pio::PacketIn.new(raw_data: data_dump) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_length | 94 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | buffer_id | 0 | @@ -51,25 +46,3 @@ Feature: Pio::PacketIn | raw_data.length | 60 | | source_mac | ac:5d:10:31:37:79 | | destination_mac | ff:ff:ff:ff:ff:ff | - - Scenario: read - When I try to parse a file named "open_flow13/packet_in.raw" with "PacketIn" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 10 | - | message_length | 102 | - | transaction_id | 123 | - | xid | 123 | - | buffer_id.to_hex | 0xcafebabe | - | total_len | 60 | - | in_port | 1 | - | reason | :no_match | - | table_id | 0 | - | cookie | 0 | - | match.match_fields.size | 1 | - | match.match_fields[0].in_port | 1 | - | raw_data.length | 60 | - | source_mac | ac:5d:10:31:37:79 | - | destination_mac | ff:ff:ff:ff:ff:ff | diff --git a/features/open_flow13/packet_out.feature b/features/open_flow13/packet_out.feature index 0aa520ae..f7caf636 100644 --- a/features/open_flow13/packet_out.feature +++ b/features/open_flow13/packet_out.feature @@ -1,16 +1,13 @@ @open_flow13 -Feature: Pio::PacketOut +Feature: PacketOut Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::PacketOut.new """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 13 | - | message_length | 24 | + | version | 4 | | to_binary.length | 24 | | transaction_id | 0 | | xid | 0 | @@ -20,7 +17,7 @@ Feature: Pio::PacketOut | raw_data.length | 0 | Scenario: new (actions = SendOutPort(1), raw_data = ARP Request) - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ data_dump = [ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xac, 0x5d, 0x10, 0x31, 0x37, @@ -33,12 +30,9 @@ Feature: Pio::PacketOut Pio::PacketOut.new(raw_data: data_dump, actions: Pio::SendOutPort.new(1)) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 13 | - | message_length | 100 | + | version | 4 | | to_binary.length | 100 | | transaction_id | 0 | | xid | 0 | @@ -59,35 +53,3 @@ Feature: Pio::PacketOut | sender_protocol_address | 192.168.2.254 | | target_hardware_address | ff:ff:ff:ff:ff:ff | | target_protocol_address | 192.168.2.5 | - - Scenario: read - When I try to parse a file named "open_flow13/packet_out.raw" with "PacketOut" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | ofp_version | 4 | - | message_type | 13 | - | message_length | 100 | - | transaction_id | 123 | - | xid | 123 | - | buffer_id | :no_buffer | - | in_port | :controller | - | actions_length | 16 | - | actions.first.class | Pio::OpenFlow13::SendOutPort | - | actions.first.port | 1 | - | actions.first.max_length | :no_buffer | - | raw_data.length | 60 | - | data.class | Pio::Arp::Request | - | destination_mac | ff:ff:ff:ff:ff:ff | - | source_mac | ac:5d:10:31:37:79 | - | ether_type | 2054 | - | hardware_type | 1 | - | protocol_type | 2048 | - | hardware_length | 6 | - | protocol_length | 4 | - | operation | 1 | - | sender_hardware_address | ac:5d:10:31:37:79 | - | sender_protocol_address | 192.168.2.254 | - | target_hardware_address | ff:ff:ff:ff:ff:ff | - | target_protocol_address | 192.168.2.5 | - diff --git a/features/open_flow13/send_out_port.feature b/features/open_flow13/send_out_port.feature index ad7f136e..987243c1 100644 --- a/features/open_flow13/send_out_port.feature +++ b/features/open_flow13/send_out_port.feature @@ -1,12 +1,11 @@ @open_flow13 -Feature: Pio::SendOutPort +Feature: SendOutPort Scenario: new(1) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(1) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | @@ -14,12 +13,11 @@ Feature: Pio::SendOutPort | max_length | :no_buffer | Scenario: new(:all) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:all) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | @@ -27,12 +25,11 @@ Feature: Pio::SendOutPort | max_length | :no_buffer | Scenario: new(:controller) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:controller) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | @@ -40,12 +37,11 @@ Feature: Pio::SendOutPort | max_length | :no_buffer | Scenario: new(:local) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:local) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | @@ -53,12 +49,11 @@ Feature: Pio::SendOutPort | max_length | :no_buffer | Scenario: new(:table) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:table) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | @@ -66,12 +61,11 @@ Feature: Pio::SendOutPort | max_length | :no_buffer | Scenario: new(:in_port) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:in_port) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | @@ -79,12 +73,11 @@ Feature: Pio::SendOutPort | max_length | :no_buffer | Scenario: new(:normal) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:normal) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | @@ -92,12 +85,11 @@ Feature: Pio::SendOutPort | max_length | :no_buffer | Scenario: new(:flood) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SendOutPort.new(:flood) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | @@ -105,9 +97,8 @@ Feature: Pio::SendOutPort | max_length | :no_buffer | Scenario: read - When I try to parse a file named "open_flow13/send_out_port.raw" with "Pio::SendOutPort" class - Then it should finish successfully - And the message has the following fields and values: + When I parse a file named "open_flow13/send_out_port.raw" with "Pio::SendOutPort" class + Then the message has the following fields and values: | field | value | | action_type | 0 | | action_length | 16 | diff --git a/features/open_flow13/set_arp_operation.feature b/features/open_flow13/set_arp_operation.feature index 9086b17e..876b9d8e 100644 --- a/features/open_flow13/set_arp_operation.feature +++ b/features/open_flow13/set_arp_operation.feature @@ -1,13 +1,12 @@ @open_flow13 -Feature: Pio::SetArpOperation +Feature: SetArpOperation Scenario: new(Pio::Arp::Reply::OPERATION) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ - Pio::SetArpOperation.new(Pio::Arp::Reply::OPERATION) + Pio::SetArpOperation.new(Pio::Arp::Reply.operation) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 25 | | action_length | 16 | diff --git a/features/open_flow13/set_arp_sender_hardware_address.feature b/features/open_flow13/set_arp_sender_hardware_address.feature index ab6ac774..a8859fb6 100644 --- a/features/open_flow13/set_arp_sender_hardware_address.feature +++ b/features/open_flow13/set_arp_sender_hardware_address.feature @@ -1,13 +1,12 @@ @open_flow13 -Feature: Pio::SetArpSenderHardwareAddress +Feature: SetArpSenderHardwareAddress Scenario: new('00:00:de:ad:be:ef') - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SetArpSenderHardwareAddress.new('00:00:de:ad:be:ef') """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 25 | | action_length | 16 | diff --git a/features/open_flow13/set_arp_sender_protocol_address.feature b/features/open_flow13/set_arp_sender_protocol_address.feature index 2d603a4e..c0b59632 100644 --- a/features/open_flow13/set_arp_sender_protocol_address.feature +++ b/features/open_flow13/set_arp_sender_protocol_address.feature @@ -1,13 +1,12 @@ @open_flow13 -Feature: Pio::SetArpSenderProtocolAddress +Feature: SetArpSenderProtocolAddress Scenario: new('192.168.1.1') - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SetArpSenderProtocolAddress.new('192.168.1.1') """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 25 | | action_length | 16 | diff --git a/features/open_flow13/set_destination_mac_address.feature b/features/open_flow13/set_destination_mac_address.feature index 814d6a90..b013cd6f 100644 --- a/features/open_flow13/set_destination_mac_address.feature +++ b/features/open_flow13/set_destination_mac_address.feature @@ -1,13 +1,12 @@ @open_flow13 -Feature: Pio::SetDestinationMacAddress +Feature: SetDestinationMacAddress Scenario: new('11:22:33:44:55:66') - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SetDestinationMacAddress.new('11:22:33:44:55:66') """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 25 | | action_length | 16 | diff --git a/features/open_flow13/set_ip_ttl.feature b/features/open_flow13/set_ip_ttl.feature new file mode 100644 index 00000000..8fd75b49 --- /dev/null +++ b/features/open_flow13/set_ip_ttl.feature @@ -0,0 +1,12 @@ +@open_flow13 +Feature: SetIpTtl + + Scenario: new(10) + When I create an OpenFlow action with: + """ + Pio::SetIpTtl.new(10) + """ + Then the action has the following fields and values: + | field | value | + | action_type | 23 | + | ttl | 10 | diff --git a/features/open_flow13/set_metadata.feature b/features/open_flow13/set_metadata.feature index f7f8cad3..556bdcd0 100644 --- a/features/open_flow13/set_metadata.feature +++ b/features/open_flow13/set_metadata.feature @@ -1,13 +1,12 @@ @open_flow13 -Feature: Pio::SetMetadata +Feature: SetMetadata Scenario: new(0x123) - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SetMetadata.new(0x123) """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 25 | | action_length | 16 | diff --git a/features/open_flow13/set_source_mac_address.feature b/features/open_flow13/set_source_mac_address.feature index 78c98ab2..84c74e20 100644 --- a/features/open_flow13/set_source_mac_address.feature +++ b/features/open_flow13/set_source_mac_address.feature @@ -1,13 +1,12 @@ @open_flow13 -Feature: Pio::SetSourceMacAddress +Feature: SetSourceMacAddress Scenario: new('11:22:33:44:55:66') - When I try to create an OpenFlow action with: + When I create an OpenFlow action with: """ Pio::SetSourceMacAddress.new('11:22:33:44:55:66') """ - Then it should finish successfully - And the action has the following fields and values: + Then the action has the following fields and values: | field | value | | action_type | 25 | | action_length | 16 | diff --git a/features/open_flow13/stats_request.feature b/features/open_flow13/stats_request.feature index 24f739e0..5cbb57c8 100644 --- a/features/open_flow13/stats_request.feature +++ b/features/open_flow13/stats_request.feature @@ -1,19 +1,16 @@ -Feature: Pio::StatsRequest +Feature: StatsRequest Background: Given I use OpenFlow 1.3 @wip Scenario: new - When I try to create an OpenFlow message with: + When I create an OpenFlow message with: """ Pio::StatsRequest.new(stats_type: :table) """ - Then it should finish successfully - And the message has the following fields and values: + Then the message has the following fields and values: | field | value | - | ofp_version | 4 | - | message_type | 16 | - | message_length | 12 | + | version | 4 | | transaction_id | 0 | | xid | 0 | | stats_type | :table | diff --git a/features/open_flow13/write_metadata.feature b/features/open_flow13/write_metadata.feature index fb46a56a..eff2e050 100644 --- a/features/open_flow13/write_metadata.feature +++ b/features/open_flow13/write_metadata.feature @@ -1,28 +1,26 @@ @open_flow13 -Feature: Pio::WriteMetadata +Feature: WriteMetadata Scenario: new(metadata: 1) - When I try to create an OpenFlow instruction with: + When I create an OpenFlow instruction with: """ - Pio::WriteMetadata.new(metadata: 1) + Pio::OpenFlow13::WriteMetadata.new(metadata: 1) """ - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | class | Pio::WriteMetadata | - | instruction_type | 2 | - | instruction_length | 24 | - | to_binary_s.length | 24 | - | metadata | 1 | - | metadata_mask | 0 | + Then the message has the following fields and values: + | field | value | + | class | Pio::OpenFlow13::WriteMetadata | + | instruction_type | 2 | + | instruction_length | 24 | + | to_binary_s.length | 24 | + | metadata | 1 | + | metadata_mask | 0 | Scenario: read - When I try to parse a file named "open_flow13/instruction_write_metadata.raw" with "Pio::WriteMetadata" class - Then it should finish successfully - And the message has the following fields and values: - | field | value | - | class | Pio::WriteMetadata | - | instruction_type | 2 | - | instruction_length | 24 | - | to_binary_s.length | 24 | - | metadata | 1 | - | metadata_mask | 1 | + When I parse a file named "open_flow13/instruction_write_metadata.raw" with "Pio::OpenFlow13::WriteMetadata" class + Then the message has the following fields and values: + | field | value | + | class | Pio::OpenFlow13::WriteMetadata | + | instruction_type | 2 | + | instruction_length | 24 | + | to_binary_s.length | 24 | + | metadata | 1 | + | metadata_mask | 1 | diff --git a/features/open_flow_read.feature b/features/open_flow_read.feature index 5a24e237..efba0956 100644 --- a/features/open_flow_read.feature +++ b/features/open_flow_read.feature @@ -1,5 +1,5 @@ -Feature: Pio::OpenFlow.read - Scenario: OpenFlow10 +Feature: OpenFlow.read + Scenario: OpenFlow 1.0 Given I switch the Pio::OpenFlow version to "OpenFlow10" Then the following each raw file should be parsed into its corresponding object using OpenFlow.read | raw file | result object | @@ -19,8 +19,27 @@ Feature: Pio::OpenFlow.read | open_flow10/flow_stats_request.raw | Pio::OpenFlow10::FlowStats::Request | | open_flow10/hello.raw | Pio::OpenFlow10::Hello | | open_flow10/hello_failed.raw | Pio::OpenFlow10::Error::HelloFailed | - | open_flow10/packet_in.raw | Pio::OpenFlow10::PacketIn | | open_flow10/packet_out.raw | Pio::OpenFlow10::PacketOut | + | open_flow10/port_stats_request.raw | Pio::OpenFlow10::PortStats::Request | | open_flow10/port_status.raw | Pio::OpenFlow10::PortStatus | - | open_flow13/bad_request.raw | Pio::OpenFlow13::Error::BadRequest | - | open_flow13/hello_failed.raw | Pio::OpenFlow13::Error::HelloFailed | + | open_flow10/queue_stats_request.raw | Pio::OpenFlow10::QueueStats::Request | + | open_flow10/table_stats_request.raw | Pio::OpenFlow10::TableStats::Request | + + Scenario: OpenFlow 1.3 + Given I switch the Pio::OpenFlow version to "OpenFlow13" + Then the following each raw file should be parsed into its corresponding object using OpenFlow.read + | raw file | result object | + | open_flow13/bad_request.raw | Pio::OpenFlow13::Error::BadRequest | + | open_flow13/echo_reply_body.raw | Pio::OpenFlow13::Echo::Reply | + | open_flow13/echo_reply_no_body.raw | Pio::OpenFlow13::Echo::Reply | + | open_flow13/echo_request_body.raw | Pio::OpenFlow13::Echo::Request | + | open_flow13/echo_request_no_body.raw | Pio::OpenFlow13::Echo::Request | + | open_flow13/features_reply.raw | Pio::OpenFlow13::Features::Reply | + | open_flow13/features_request.raw | Pio::OpenFlow13::Features::Request | + | open_flow13/flow_mod_add_apply_no_match.raw | Pio::OpenFlow13::FlowMod | + | open_flow13/flow_mod_no_match_or_instructions.raw | Pio::OpenFlow13::FlowMod | + | open_flow13/hello_failed.raw | Pio::OpenFlow13::Error::HelloFailed | + | open_flow13/hello_no_version_bitmap.raw | Pio::OpenFlow13::Hello | + | open_flow13/hello_version_bitmap.raw | Pio::OpenFlow13::Hello | + | open_flow13/packet_in.raw | Pio::OpenFlow13::PacketIn | + | open_flow13/packet_out.raw | Pio::OpenFlow13::PacketOut | diff --git a/features/open_flow_version.feature b/features/open_flow_version.feature index 35882549..9c9052a3 100644 --- a/features/open_flow_version.feature +++ b/features/open_flow_version.feature @@ -1,4 +1,4 @@ -Feature: Pio::OpenFlow.version +Feature: OpenFlow.version Scenario: OpenFlow 1.0 Given I switch the Pio::OpenFlow version to "OpenFlow10" When I get the OpenFlow version string diff --git a/features/parser.feature b/features/parser.feature index 6c4d2466..8e14dd93 100644 --- a/features/parser.feature +++ b/features/parser.feature @@ -1,10 +1,9 @@ -Feature: Pio::Parser +Feature: Parser Scenario: parse icmpv6.pcap - When I try to parse a file named "icmpv6.pcap" with "Pio::Parser" class - Then it should finish successfully - And the message #1 have the following fields and values: - | field | value | - | class | Pio::Parser::EthernetFrame | - | destination_mac | 00:60:97:07:69:ea | - | source_mac | 00:00:86:05:80:da | - | ether_type | 34525 | + When I parse a file named "icmpv6.pcap" with "Pio::Parser" class + Then the message #1 have the following fields and values: + | field | value | + | class | Pio::EthernetFrame | + | destination_mac | 00:60:97:07:69:ea | + | source_mac | 00:00:86:05:80:da | + | ether_type | 34525 | diff --git a/features/step_definitions/LICENSE b/features/step_definitions/LICENSE deleted file mode 100644 index 6b156fe1..00000000 --- a/features/step_definitions/LICENSE +++ /dev/null @@ -1,675 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. - diff --git a/features/step_definitions/open_flow_steps.rb b/features/step_definitions/open_flow_steps.rb index 429c5fe9..78801ac4 100644 --- a/features/step_definitions/open_flow_steps.rb +++ b/features/step_definitions/open_flow_steps.rb @@ -1,40 +1,35 @@ Given(/^I switch the Pio::OpenFlow version to "([^"]*)"$/) do |version| - Pio::OpenFlow.switch_version version.to_sym + Pio::OpenFlow.version = version end When(/^I get the OpenFlow version string$/) do - @version = Pio::OpenFlow.version + @version = Pio::OpenFlow.version.to_s end Then(/^the version string should be "([^"]*)"$/) do |expected_version_string| expect(@version).to eq(expected_version_string) end -When(/^I try to create a packet with:$/) do |ruby_code| - begin - @result = Pio.module_eval(ruby_code) - rescue - @last_error = $ERROR_INFO - end +When(/^I create a packet with:$/) do |ruby_code| + cd('.') { @result = Pio.module_eval(ruby_code) } end -When(/^I try to create an OpenFlow message with:$/) do |ruby_code| - step 'I try to create a packet with:', ruby_code +When(/^I create an OpenFlow message with:$/) do |ruby_code| + step 'I create a packet with:', ruby_code end -When(/^I try to create an OpenFlow action with:$/) do |ruby_code| - step 'I try to create a packet with:', ruby_code +When(/^I create an OpenFlow action with:$/) do |ruby_code| + step 'I create a packet with:', ruby_code end -When(/^I try to create an OpenFlow instruction with:$/) do |ruby_code| - step 'I try to create a packet with:', ruby_code +When(/^I create an OpenFlow instruction with:$/) do |ruby_code| + step 'I create a packet with:', ruby_code end # rubocop:disable LineLength Then(/^the following each raw file should be parsed into its corresponding object using OpenFlow\.read$/) do |table| table.hashes.each do |each| - step %(I try to parse a file named "#{each['raw file']}" with "OpenFlow" class) - step 'it should finish successfully' + step %(I parse a file named "#{each['raw file']}" with "Pio::OpenFlow" class) step %(the message should be a "#{each['result object']}") end end diff --git a/features/step_definitions/packet_data_steps.rb b/features/step_definitions/packet_data_steps.rb index fcbaebc0..8858d96c 100644 --- a/features/step_definitions/packet_data_steps.rb +++ b/features/step_definitions/packet_data_steps.rb @@ -1,43 +1,27 @@ -# rubocop:disable LineLength -When(/^I try to parse a file named "(.*?\.raw)" with "(.*?)" class$/) do |path, klass| - full_path = File.expand_path(File.join(__dir__, '..', path)) - raw_data = IO.read(full_path) +When(/^I parse a file named "(.*?\.raw)" with "(.*?)" class$/) do |path, klass| + raw_data = IO.read(expand_path("%/#{path}")) parser_klass = Pio.const_get(klass) - begin - @result = parser_klass.read(raw_data) - rescue - @last_error = $ERROR_INFO - end + @result = parser_klass.read(raw_data) end -# rubocop:enable LineLength -# rubocop:disable LineLength -When(/^I try to parse a file named "(.*?\.pcap)" with "(.*?)" class$/) do |path, klass| - full_path = File.expand_path(File.join(__dir__, '..', path)) - pcap = Pio::Pcap::Frame.read(IO.read(full_path)) +When(/^I parse a file named "(.*?\.pcap)" with "(.*?)" class$/) do |path, klass| + pcap = Pio::Pcap::Frame.read(IO.read(expand_path("%/#{path}"))) parser_klass = Pio.const_get(klass) - begin - @result = pcap.records.each_with_object([]) do |each, result| - result << parser_klass.read(each.data) - end - rescue - @last_error = $ERROR_INFO + @result = pcap.records.each_with_object([]) do |each, result| + result << parser_klass.read(each.data) end end -# rubocop:enable LineLength - -Then(/^it should finish successfully$/) do - expect(@last_error).to be_nil -end - -Then(/^it should fail with "([^"]*)", "([^"]*)"$/) do |error, message| - expect(@last_error.class.to_s).to eq(error) - expect(@last_error.message).to eq(message) -end When(/^I create an exact match from "(.*?)"$/) do |path| - full_path = File.expand_path(File.join(__dir__, '..', path)) - @result = Pio::ExactMatch.new(Pio::PacketIn.read(IO.read(full_path))) + raw_data = case File.extname(path) + when '.raw' + IO.read(expand_path("%/#{path}")) + when '.rb' + Pio.module_eval(IO.read(expand_path("%/#{path}"))) + else + raise + end + @result = Pio::ExactMatch.new(Pio::PacketIn.read(raw_data)) end Then(/^the message should be a "([^"]*)"$/) do |expected_klass| diff --git a/features/step_definitions/ruby_steps.rb b/features/step_definitions/ruby_steps.rb new file mode 100644 index 00000000..08c98437 --- /dev/null +++ b/features/step_definitions/ruby_steps.rb @@ -0,0 +1,7 @@ +When(/^I eval the following Ruby code:$/) do |ruby_code| + @result = Pio.module_eval(ruby_code) +end + +Then(/^the result of eval should be:$/) do |expected| + expect(@result.to_s).to eq expected +end diff --git a/features/step_definitions/show_stats_steps.rb b/features/step_definitions/show_stats_steps.rb index 6e1f1b2c..00a42e3d 100644 --- a/features/step_definitions/show_stats_steps.rb +++ b/features/step_definitions/show_stats_steps.rb @@ -15,7 +15,7 @@ when /-> (\S+) = (\d+) packet/ result[Regexp.last_match(1)] = Regexp.last_match(2).to_i else - fail "Failed to parse line '#{each}'" + raise "Failed to parse line '#{each}'" end end end @@ -43,7 +43,7 @@ next unless received result[Regexp.last_match(1)] = Regexp.last_match(3).to_i else - fail "Failed to parse line '#{each}'" + raise "Failed to parse line '#{each}'" end end end @@ -72,7 +72,7 @@ next unless received result += Regexp.last_match(3).to_i else - fail "Failed to parse line '#{each}'" + raise "Failed to parse line '#{each}'" end end end diff --git a/features/support/env.rb b/features/support/env.rb index 618b0bbc..c4dc53e5 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,5 +1,6 @@ require 'coveralls' Coveralls.wear! +require 'aruba/cucumber' require 'pio' require 'pio/pcap' diff --git a/features/support/hooks.rb b/features/support/hooks.rb index aea8c2bb..99208b24 100644 --- a/features/support/hooks.rb +++ b/features/support/hooks.rb @@ -1,7 +1,7 @@ Before('@open_flow10') do - Pio::OpenFlow.switch_version :OpenFlow10 + Pio::OpenFlow.version = :OpenFlow10 end Before('@open_flow13') do - Pio::OpenFlow.switch_version :OpenFlow13 + Pio::OpenFlow.version = :OpenFlow13 end diff --git a/features/udp.feature b/features/udp.feature index e8393a34..ff302f86 100644 --- a/features/udp.feature +++ b/features/udp.feature @@ -1,28 +1,27 @@ -Feature: Pio::Udp +Feature: Udp Scenario: parse dhcp.pcap - When I try to parse a file named "dhcp.pcap" with "Pio::Udp" class - Then it should finish successfully - And the message #1 have the following fields and values: - | field | value | - | class | Pio::Udp | - | destination_mac | ff:ff:ff:ff:ff:ff | - | source_mac | 00:0b:82:01:fc:42 | - | ether_type | 2048 | - | ip_version | 4 | - | ip_header_length | 5 | - | ip_type_of_service | 0 | - | ip_total_length | 300 | - | ip_identifier | 43062 | - | ip_flag | 0 | - | ip_fragment | 0 | - | ip_ttl | 250 | - | ip_protocol | 17 | - | ip_header_checksum | 6027 | - | source_ip_address | 0.0.0.0 | - | destination_ip_address | 255.255.255.255 | - | ip_option | | - | udp_source_port | 68 | - | udp_destination_port | 67 | - | udp_length | 280 | - | udp_checksum | 22815 | - | udp_payload.length | 272 | + When I parse a file named "dhcp.pcap" with "Pio::Udp" class + Then the message #1 have the following fields and values: + | field | value | + | class | Pio::Udp | + | destination_mac | ff:ff:ff:ff:ff:ff | + | source_mac | 00:0b:82:01:fc:42 | + | ether_type.to_hex | 0x800 | + | ip_version | 4 | + | ip_header_length | 5 | + | ip_type_of_service | 0 | + | ip_total_length | 300 | + | ip_identifier | 43062 | + | ip_flag | 0 | + | ip_fragment | 0 | + | ip_ttl | 250 | + | ip_protocol | 17 | + | ip_header_checksum | 6027 | + | source_ip_address | 0.0.0.0 | + | destination_ip_address | 255.255.255.255 | + | ip_option | | + | udp_source_port | 68 | + | udp_destination_port | 67 | + | udp_length | 280 | + | udp_checksum | 22815 | + | udp_payload.length | 272 | diff --git a/fixtures/arp/arp_reply.rb b/fixtures/arp/arp_reply.rb new file mode 100644 index 00000000..7d3b48d9 --- /dev/null +++ b/fixtures/arp/arp_reply.rb @@ -0,0 +1,14 @@ +[ + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # destination_mac + 0x00, 0x16, 0x9d, 0x1d, 0x9c, 0xc4, # source_mac + 0x08, 0x06, # ether_type + 0x00, 0x01, # hardware_type + 0x08, 0x00, # protocol_type + 0x06, # hardware_length + 0x04, # protocol_length + 0x00, 0x02, # operation + 0x00, 0x16, 0x9d, 0x1d, 0x9c, 0xc4, # sender_hardware_address + 0xc0, 0xa8, 0x53, 0xfe, # sender_protocol_address + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # target_hardware_address + 0xc0, 0xa8, 0x53, 0x03, # target_protocol_address +].pack('C*') diff --git a/fixtures/arp/arp_request.rb b/fixtures/arp/arp_request.rb new file mode 100644 index 00000000..bf5d4d6b --- /dev/null +++ b/fixtures/arp/arp_request.rb @@ -0,0 +1,14 @@ +[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac + 0x08, 0x06, # ether_type + 0x00, 0x01, # hardware_type + 0x08, 0x00, # protocol_type + 0x06, # hardware_length + 0x04, # protocol_length + 0x00, 0x01, # operation + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # sender_hardware_address + 0xc0, 0xa8, 0x53, 0x03, # sender_protocol_address + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # target_hardware_address + 0xc0, 0xa8, 0x53, 0xfe, # target_protocol_address +].pack('C*') diff --git a/features/dhcp.pcap b/fixtures/dhcp.pcap similarity index 100% rename from features/dhcp.pcap rename to fixtures/dhcp.pcap diff --git a/fixtures/ethernet_header/ethernet_header.rb b/fixtures/ethernet_header/ethernet_header.rb new file mode 100644 index 00000000..739268c9 --- /dev/null +++ b/fixtures/ethernet_header/ethernet_header.rb @@ -0,0 +1,5 @@ +[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac + 0x08, 0x00, # ether_type +].pack('C*') diff --git a/fixtures/ethernet_header/vlan_ethernet_header.rb b/fixtures/ethernet_header/vlan_ethernet_header.rb new file mode 100644 index 00000000..0de1b379 --- /dev/null +++ b/fixtures/ethernet_header/vlan_ethernet_header.rb @@ -0,0 +1,7 @@ +[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, # destination_mac + 0x00, 0x26, 0x82, 0xeb, 0xea, 0xd1, # source_mac + 0x81, 0x00, # ether_type + 0b101_0_000001100100, # vlan_pcp, vlan_cfi, vlan_vid + 0x81, 0x00, # ether_type_vlan +].pack('C14nC2') diff --git a/fixtures/icmp/icmp_reply.rb b/fixtures/icmp/icmp_reply.rb new file mode 100644 index 00000000..f811d54c --- /dev/null +++ b/fixtures/icmp/icmp_reply.rb @@ -0,0 +1,21 @@ +[ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, # destination_mac + 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, # source_mac + 0x08, 0x00, # ether_type + 0b0100_0101, # ip_version, ip_header_length + 0x00, # ip_type_of_service + 0x00, 0x36, # ip_total_length + 0x00, 0x00, # ip_identifier + 0b000_0000000000000, # ip_flag, ip_fragment + 0x80, # ip_ttl + 0x01, # ip_protocol + 0x12, 0x75, # ip_header_checksum + 0xc0, 0xa8, 0x53, 0xfe, # source_ip_address + 0xc0, 0xa8, 0x53, 0x03, # destination_ip_address + 0x00, # icmp_type + 0x00, # icmp_code + 0x6f, 0xf5, # icmp_checksum + 0x01, 0x00, # icmp_identifier + 0x00, 0x6f, # icmp_sequence_number + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, # echo_data +].pack('C20nC46') diff --git a/fixtures/icmp/icmp_request.rb b/fixtures/icmp/icmp_request.rb new file mode 100644 index 00000000..2a6e826c --- /dev/null +++ b/fixtures/icmp/icmp_request.rb @@ -0,0 +1,21 @@ +[ + 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, # destination_mac + 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, # source_mac + 0x08, 0x00, # ether_type + 0b0100_0101, # ip_version, ip_header_length + 0x00, # ip_type_of_service + 0x00, 0x36, # ip_total_length + 0x00, 0x00, # ip_identifier + 0b000_0000000000000, # ip_flag, ip_fragment + 0x80, # ip_ttl + 0x01, # ip_protocol + 0x12, 0x75, # ip_header_checksum + 0xc0, 0xa8, 0x53, 0x03, # source_ip_address + 0xc0, 0xa8, 0x53, 0xfe, # destination_ip_address + 0x08, # icmp_type + 0x00, # icmp_code + 0x67, 0xf5, # icmp_checksum + 0x01, 0x00, # icmp_identifier + 0x00, 0x6f, # icmp_sequence_number + 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, # echo_data +].pack('C20nC46') diff --git a/features/icmpv6.pcap b/fixtures/icmpv6.pcap similarity index 100% rename from features/icmpv6.pcap rename to fixtures/icmpv6.pcap diff --git a/fixtures/ipv4_header/ipv4_header.rb b/fixtures/ipv4_header/ipv4_header.rb new file mode 100644 index 00000000..c8e19d93 --- /dev/null +++ b/fixtures/ipv4_header/ipv4_header.rb @@ -0,0 +1,12 @@ +[ + 0b0100_0101, # ip_version, ip_header_length + 0x00, # ip_type_of_service + 0x00, 0x14, # ip_total_length + 0x00, 0x00, # ip_identifier + 0b000_0000000000000, # ip_flag, ip_fragment + 0x80, # ip_ttl + 0x00, # ip_protocol + 0x30, 0xe1, # ip_header_checksum + 0x01, 0x02, 0x03, 0x04, # source_ip_address + 0x04, 0x03, 0x02, 0x01, # destination_ip_address +].pack('C6nC12') diff --git a/features/lldp.detailed.pcap b/fixtures/lldp.detailed.pcap similarity index 100% rename from features/lldp.detailed.pcap rename to fixtures/lldp.detailed.pcap diff --git a/features/lldp.minimal.pcap b/fixtures/lldp.minimal.pcap similarity index 100% rename from features/lldp.minimal.pcap rename to fixtures/lldp.minimal.pcap diff --git a/features/open_flow10/aggregate_stats_reply.raw b/fixtures/open_flow10/aggregate_stats_reply.raw similarity index 100% rename from features/open_flow10/aggregate_stats_reply.raw rename to fixtures/open_flow10/aggregate_stats_reply.raw diff --git a/features/open_flow10/aggregate_stats_request.raw b/fixtures/open_flow10/aggregate_stats_request.raw similarity index 100% rename from features/open_flow10/aggregate_stats_request.raw rename to fixtures/open_flow10/aggregate_stats_request.raw diff --git a/fixtures/open_flow10/bad_action_bad_argument.raw b/fixtures/open_flow10/bad_action_bad_argument.raw new file mode 100644 index 00000000..caf8d5f9 Binary files /dev/null and b/fixtures/open_flow10/bad_action_bad_argument.raw differ diff --git a/fixtures/open_flow10/bad_action_bad_length.raw b/fixtures/open_flow10/bad_action_bad_length.raw new file mode 100644 index 00000000..b0aeae1c Binary files /dev/null and b/fixtures/open_flow10/bad_action_bad_length.raw differ diff --git a/fixtures/open_flow10/bad_action_bad_out_port.raw b/fixtures/open_flow10/bad_action_bad_out_port.raw new file mode 100644 index 00000000..38d0ad6d Binary files /dev/null and b/fixtures/open_flow10/bad_action_bad_out_port.raw differ diff --git a/fixtures/open_flow10/bad_action_bad_queue.raw b/fixtures/open_flow10/bad_action_bad_queue.raw new file mode 100644 index 00000000..f38f42f1 Binary files /dev/null and b/fixtures/open_flow10/bad_action_bad_queue.raw differ diff --git a/fixtures/open_flow10/bad_action_bad_type.raw b/fixtures/open_flow10/bad_action_bad_type.raw new file mode 100644 index 00000000..9e077d57 Binary files /dev/null and b/fixtures/open_flow10/bad_action_bad_type.raw differ diff --git a/fixtures/open_flow10/bad_action_bad_vendor.raw b/fixtures/open_flow10/bad_action_bad_vendor.raw new file mode 100644 index 00000000..099de538 Binary files /dev/null and b/fixtures/open_flow10/bad_action_bad_vendor.raw differ diff --git a/fixtures/open_flow10/bad_action_bad_vendor_type.raw b/fixtures/open_flow10/bad_action_bad_vendor_type.raw new file mode 100644 index 00000000..3d550f1c Binary files /dev/null and b/fixtures/open_flow10/bad_action_bad_vendor_type.raw differ diff --git a/fixtures/open_flow10/bad_action_eperm.raw b/fixtures/open_flow10/bad_action_eperm.raw new file mode 100644 index 00000000..27e1179f Binary files /dev/null and b/fixtures/open_flow10/bad_action_eperm.raw differ diff --git a/fixtures/open_flow10/bad_action_too_many.raw b/fixtures/open_flow10/bad_action_too_many.raw new file mode 100644 index 00000000..5a9aad1c Binary files /dev/null and b/fixtures/open_flow10/bad_action_too_many.raw differ diff --git a/features/open_flow10/bad_request.raw b/fixtures/open_flow10/bad_request.raw similarity index 100% rename from features/open_flow10/bad_request.raw rename to fixtures/open_flow10/bad_request.raw diff --git a/features/open_flow10/barrier_reply.raw b/fixtures/open_flow10/barrier_reply.raw similarity index 100% rename from features/open_flow10/barrier_reply.raw rename to fixtures/open_flow10/barrier_reply.raw diff --git a/features/open_flow10/barrier_request.raw b/fixtures/open_flow10/barrier_request.raw similarity index 100% rename from features/open_flow10/barrier_request.raw rename to fixtures/open_flow10/barrier_request.raw diff --git a/features/open_flow10/description_stats_reply.raw b/fixtures/open_flow10/description_stats_reply.raw similarity index 100% rename from features/open_flow10/description_stats_reply.raw rename to fixtures/open_flow10/description_stats_reply.raw diff --git a/features/open_flow10/description_stats_request.raw b/fixtures/open_flow10/description_stats_request.raw similarity index 100% rename from features/open_flow10/description_stats_request.raw rename to fixtures/open_flow10/description_stats_request.raw diff --git a/features/open_flow10/echo_reply.raw b/fixtures/open_flow10/echo_reply.raw similarity index 100% rename from features/open_flow10/echo_reply.raw rename to fixtures/open_flow10/echo_reply.raw diff --git a/features/open_flow10/echo_request.raw b/fixtures/open_flow10/echo_request.raw similarity index 100% rename from features/open_flow10/echo_request.raw rename to fixtures/open_flow10/echo_request.raw diff --git a/features/open_flow10/error.raw b/fixtures/open_flow10/error.raw similarity index 100% rename from features/open_flow10/error.raw rename to fixtures/open_flow10/error.raw diff --git a/features/open_flow10/features_reply.raw b/fixtures/open_flow10/features_reply.raw similarity index 100% rename from features/open_flow10/features_reply.raw rename to fixtures/open_flow10/features_reply.raw diff --git a/features/open_flow10/features_request.raw b/fixtures/open_flow10/features_request.raw similarity index 100% rename from features/open_flow10/features_request.raw rename to fixtures/open_flow10/features_request.raw diff --git a/features/open_flow10/flow_mod_add.raw b/fixtures/open_flow10/flow_mod_add.raw similarity index 100% rename from features/open_flow10/flow_mod_add.raw rename to fixtures/open_flow10/flow_mod_add.raw diff --git a/features/open_flow10/flow_mod_delete.raw b/fixtures/open_flow10/flow_mod_delete.raw similarity index 100% rename from features/open_flow10/flow_mod_delete.raw rename to fixtures/open_flow10/flow_mod_delete.raw diff --git a/features/open_flow10/flow_mod_delete_strict.raw b/fixtures/open_flow10/flow_mod_delete_strict.raw similarity index 100% rename from features/open_flow10/flow_mod_delete_strict.raw rename to fixtures/open_flow10/flow_mod_delete_strict.raw diff --git a/fixtures/open_flow10/flow_mod_failed_all_tables_full.raw b/fixtures/open_flow10/flow_mod_failed_all_tables_full.raw new file mode 100644 index 00000000..8b3732c5 Binary files /dev/null and b/fixtures/open_flow10/flow_mod_failed_all_tables_full.raw differ diff --git a/fixtures/open_flow10/flow_mod_failed_bad_command.raw b/fixtures/open_flow10/flow_mod_failed_bad_command.raw new file mode 100644 index 00000000..5420f872 Binary files /dev/null and b/fixtures/open_flow10/flow_mod_failed_bad_command.raw differ diff --git a/fixtures/open_flow10/flow_mod_failed_bad_emerg_timeout.raw b/fixtures/open_flow10/flow_mod_failed_bad_emerg_timeout.raw new file mode 100644 index 00000000..bc3a0620 Binary files /dev/null and b/fixtures/open_flow10/flow_mod_failed_bad_emerg_timeout.raw differ diff --git a/fixtures/open_flow10/flow_mod_failed_eperm.raw b/fixtures/open_flow10/flow_mod_failed_eperm.raw new file mode 100644 index 00000000..83279e53 Binary files /dev/null and b/fixtures/open_flow10/flow_mod_failed_eperm.raw differ diff --git a/fixtures/open_flow10/flow_mod_failed_overlap.raw b/fixtures/open_flow10/flow_mod_failed_overlap.raw new file mode 100644 index 00000000..afd7e067 Binary files /dev/null and b/fixtures/open_flow10/flow_mod_failed_overlap.raw differ diff --git a/fixtures/open_flow10/flow_mod_failed_unsupported.raw b/fixtures/open_flow10/flow_mod_failed_unsupported.raw new file mode 100644 index 00000000..074a2998 Binary files /dev/null and b/fixtures/open_flow10/flow_mod_failed_unsupported.raw differ diff --git a/features/open_flow10/flow_mod_modify.raw b/fixtures/open_flow10/flow_mod_modify.raw similarity index 100% rename from features/open_flow10/flow_mod_modify.raw rename to fixtures/open_flow10/flow_mod_modify.raw diff --git a/features/open_flow10/flow_mod_modify_strict.raw b/fixtures/open_flow10/flow_mod_modify_strict.raw similarity index 100% rename from features/open_flow10/flow_mod_modify_strict.raw rename to fixtures/open_flow10/flow_mod_modify_strict.raw diff --git a/features/open_flow10/flow_removed.raw b/fixtures/open_flow10/flow_removed.raw similarity index 100% rename from features/open_flow10/flow_removed.raw rename to fixtures/open_flow10/flow_removed.raw diff --git a/features/open_flow10/flow_stats_reply.raw b/fixtures/open_flow10/flow_stats_reply.raw similarity index 100% rename from features/open_flow10/flow_stats_reply.raw rename to fixtures/open_flow10/flow_stats_reply.raw diff --git a/features/open_flow10/flow_stats_request.raw b/fixtures/open_flow10/flow_stats_request.raw similarity index 100% rename from features/open_flow10/flow_stats_request.raw rename to fixtures/open_flow10/flow_stats_request.raw diff --git a/features/open_flow10/get_config_reply.raw b/fixtures/open_flow10/get_config_reply.raw similarity index 100% rename from features/open_flow10/get_config_reply.raw rename to fixtures/open_flow10/get_config_reply.raw diff --git a/features/open_flow10/get_config_request.raw b/fixtures/open_flow10/get_config_request.raw similarity index 100% rename from features/open_flow10/get_config_request.raw rename to fixtures/open_flow10/get_config_request.raw diff --git a/features/open_flow10/hello.raw b/fixtures/open_flow10/hello.raw similarity index 100% rename from features/open_flow10/hello.raw rename to fixtures/open_flow10/hello.raw diff --git a/features/open_flow10/hello_failed.raw b/fixtures/open_flow10/hello_failed.raw similarity index 100% rename from features/open_flow10/hello_failed.raw rename to fixtures/open_flow10/hello_failed.raw diff --git a/features/open_flow10/nx_flow_mod_add.raw b/fixtures/open_flow10/nx_flow_mod_add.raw similarity index 100% rename from features/open_flow10/nx_flow_mod_add.raw rename to fixtures/open_flow10/nx_flow_mod_add.raw diff --git a/features/open_flow10/nx_flow_mod_delete.raw b/fixtures/open_flow10/nx_flow_mod_delete.raw similarity index 100% rename from features/open_flow10/nx_flow_mod_delete.raw rename to fixtures/open_flow10/nx_flow_mod_delete.raw diff --git a/features/open_flow10/nx_flow_mod_delete_strict.raw b/fixtures/open_flow10/nx_flow_mod_delete_strict.raw similarity index 100% rename from features/open_flow10/nx_flow_mod_delete_strict.raw rename to fixtures/open_flow10/nx_flow_mod_delete_strict.raw diff --git a/features/open_flow10/nx_flow_mod_modify.raw b/fixtures/open_flow10/nx_flow_mod_modify.raw similarity index 100% rename from features/open_flow10/nx_flow_mod_modify.raw rename to fixtures/open_flow10/nx_flow_mod_modify.raw diff --git a/features/open_flow10/nx_flow_mod_modify_strict.raw b/fixtures/open_flow10/nx_flow_mod_modify_strict.raw similarity index 100% rename from features/open_flow10/nx_flow_mod_modify_strict.raw rename to fixtures/open_flow10/nx_flow_mod_modify_strict.raw diff --git a/fixtures/open_flow10/nx_packet_in.raw b/fixtures/open_flow10/nx_packet_in.raw new file mode 100644 index 00000000..f94c29f8 Binary files /dev/null and b/fixtures/open_flow10/nx_packet_in.raw differ diff --git a/features/open_flow10/nxast_learn.raw b/fixtures/open_flow10/nxast_learn.raw similarity index 100% rename from features/open_flow10/nxast_learn.raw rename to fixtures/open_flow10/nxast_learn.raw diff --git a/fixtures/open_flow10/packet_in.rb b/fixtures/open_flow10/packet_in.rb new file mode 100644 index 00000000..84ddb816 --- /dev/null +++ b/fixtures/open_flow10/packet_in.rb @@ -0,0 +1,11 @@ +[ + 0x01, # version + 0x0a, # type + 0x00, 0x12, # _length + 0x00, 0x00, 0x00, 0x00, # transaction_id + 0xff, 0xff, 0xff, 0x00, # buffer_id + 0x00, 0x00, # total_length + 0x00, 0x01, # in_port + 0x00, # reason + 0x00, # padding +].pack('C18') diff --git a/features/open_flow10/packet_in_arp_reply.raw b/fixtures/open_flow10/packet_in_arp_reply.raw similarity index 100% rename from features/open_flow10/packet_in_arp_reply.raw rename to fixtures/open_flow10/packet_in_arp_reply.raw diff --git a/fixtures/open_flow10/packet_in_arp_request.rb b/fixtures/open_flow10/packet_in_arp_request.rb new file mode 100644 index 00000000..4921e3bc --- /dev/null +++ b/fixtures/open_flow10/packet_in_arp_request.rb @@ -0,0 +1,12 @@ +[ + 0x01, # version + 0x0a, # type + 0x00, 0x52, # _length + 0x00, 0x00, 0x00, 0x00, # transaction_id + 0xff, 0xff, 0xff, 0x00, # buffer_id + 0x00, 0x40, # total_length + 0x00, 0x01, # in_port + 0x00, # reason + 0x00, # padding + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xce, 0xb0, 0x00, 0x00, 0xcc, 0x08, 0x06, 0x00, 0x01, 0x08, 0x00, 0x06, 0x04, 0x00, 0x01, 0xfa, 0xce, 0xb0, 0x00, 0x00, 0xcc, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # raw_data +].pack('C82') diff --git a/features/open_flow10/packet_in_cbench.raw b/fixtures/open_flow10/packet_in_cbench.raw similarity index 100% rename from features/open_flow10/packet_in_cbench.raw rename to fixtures/open_flow10/packet_in_cbench.raw diff --git a/features/open_flow10/packet_out.raw b/fixtures/open_flow10/packet_out.raw similarity index 100% rename from features/open_flow10/packet_out.raw rename to fixtures/open_flow10/packet_out.raw diff --git a/features/open_flow10/port_mod.raw b/fixtures/open_flow10/port_mod.raw similarity index 100% rename from features/open_flow10/port_mod.raw rename to fixtures/open_flow10/port_mod.raw diff --git a/features/open_flow10/port_stats_reply.raw b/fixtures/open_flow10/port_stats_reply.raw similarity index 100% rename from features/open_flow10/port_stats_reply.raw rename to fixtures/open_flow10/port_stats_reply.raw diff --git a/features/open_flow10/port_stats_request.raw b/fixtures/open_flow10/port_stats_request.raw similarity index 100% rename from features/open_flow10/port_stats_request.raw rename to fixtures/open_flow10/port_stats_request.raw diff --git a/features/open_flow10/port_status.raw b/fixtures/open_flow10/port_status.raw similarity index 100% rename from features/open_flow10/port_status.raw rename to fixtures/open_flow10/port_status.raw diff --git a/features/open_flow10/queue_get_config_reply.raw b/fixtures/open_flow10/queue_get_config_reply.raw similarity index 100% rename from features/open_flow10/queue_get_config_reply.raw rename to fixtures/open_flow10/queue_get_config_reply.raw diff --git a/features/open_flow10/queue_get_config_request.raw b/fixtures/open_flow10/queue_get_config_request.raw similarity index 100% rename from features/open_flow10/queue_get_config_request.raw rename to fixtures/open_flow10/queue_get_config_request.raw diff --git a/features/open_flow10/queue_stats_request.raw b/fixtures/open_flow10/queue_stats_request.raw similarity index 100% rename from features/open_flow10/queue_stats_request.raw rename to fixtures/open_flow10/queue_stats_request.raw diff --git a/features/open_flow10/set_config.raw b/fixtures/open_flow10/set_config.raw similarity index 100% rename from features/open_flow10/set_config.raw rename to fixtures/open_flow10/set_config.raw diff --git a/features/open_flow10/table_stats_reply.raw b/fixtures/open_flow10/table_stats_reply.raw similarity index 100% rename from features/open_flow10/table_stats_reply.raw rename to fixtures/open_flow10/table_stats_reply.raw diff --git a/features/open_flow10/table_stats_request.raw b/fixtures/open_flow10/table_stats_request.raw similarity index 100% rename from features/open_flow10/table_stats_request.raw rename to fixtures/open_flow10/table_stats_request.raw diff --git a/features/open_flow10/vendor.raw b/fixtures/open_flow10/vendor.raw similarity index 100% rename from features/open_flow10/vendor.raw rename to fixtures/open_flow10/vendor.raw diff --git a/features/open_flow10/vendor_stats_request.raw b/fixtures/open_flow10/vendor_stats_request.raw similarity index 100% rename from features/open_flow10/vendor_stats_request.raw rename to fixtures/open_flow10/vendor_stats_request.raw diff --git a/features/open_flow13/action_copy_ttl_in.raw b/fixtures/open_flow13/action_copy_ttl_in.raw similarity index 100% rename from features/open_flow13/action_copy_ttl_in.raw rename to fixtures/open_flow13/action_copy_ttl_in.raw diff --git a/features/open_flow13/action_copy_ttl_out.raw b/fixtures/open_flow13/action_copy_ttl_out.raw similarity index 100% rename from features/open_flow13/action_copy_ttl_out.raw rename to fixtures/open_flow13/action_copy_ttl_out.raw diff --git a/features/open_flow13/action_dec_mpls_ttl.raw b/fixtures/open_flow13/action_dec_mpls_ttl.raw similarity index 100% rename from features/open_flow13/action_dec_mpls_ttl.raw rename to fixtures/open_flow13/action_dec_mpls_ttl.raw diff --git a/features/open_flow13/action_dec_nw_ttl.raw b/fixtures/open_flow13/action_dec_nw_ttl.raw similarity index 100% rename from features/open_flow13/action_dec_nw_ttl.raw rename to fixtures/open_flow13/action_dec_nw_ttl.raw diff --git a/features/open_flow13/action_group.raw b/fixtures/open_flow13/action_group.raw similarity index 100% rename from features/open_flow13/action_group.raw rename to fixtures/open_flow13/action_group.raw diff --git a/features/open_flow13/action_pop_mpls.raw b/fixtures/open_flow13/action_pop_mpls.raw similarity index 100% rename from features/open_flow13/action_pop_mpls.raw rename to fixtures/open_flow13/action_pop_mpls.raw diff --git a/features/open_flow13/action_pop_pbb.raw b/fixtures/open_flow13/action_pop_pbb.raw similarity index 100% rename from features/open_flow13/action_pop_pbb.raw rename to fixtures/open_flow13/action_pop_pbb.raw diff --git a/features/open_flow13/action_pop_vlan.raw b/fixtures/open_flow13/action_pop_vlan.raw similarity index 100% rename from features/open_flow13/action_pop_vlan.raw rename to fixtures/open_flow13/action_pop_vlan.raw diff --git a/features/open_flow13/action_push_mpls.raw b/fixtures/open_flow13/action_push_mpls.raw similarity index 100% rename from features/open_flow13/action_push_mpls.raw rename to fixtures/open_flow13/action_push_mpls.raw diff --git a/features/open_flow13/action_push_pbb.raw b/fixtures/open_flow13/action_push_pbb.raw similarity index 100% rename from features/open_flow13/action_push_pbb.raw rename to fixtures/open_flow13/action_push_pbb.raw diff --git a/features/open_flow13/action_push_vlan.raw b/fixtures/open_flow13/action_push_vlan.raw similarity index 100% rename from features/open_flow13/action_push_vlan.raw rename to fixtures/open_flow13/action_push_vlan.raw diff --git a/features/open_flow13/action_set_field.raw b/fixtures/open_flow13/action_set_field.raw similarity index 100% rename from features/open_flow13/action_set_field.raw rename to fixtures/open_flow13/action_set_field.raw diff --git a/features/open_flow13/action_set_mpls_ttl.raw b/fixtures/open_flow13/action_set_mpls_ttl.raw similarity index 100% rename from features/open_flow13/action_set_mpls_ttl.raw rename to fixtures/open_flow13/action_set_mpls_ttl.raw diff --git a/features/open_flow13/action_set_nw_ttl.raw b/fixtures/open_flow13/action_set_nw_ttl.raw similarity index 100% rename from features/open_flow13/action_set_nw_ttl.raw rename to fixtures/open_flow13/action_set_nw_ttl.raw diff --git a/features/open_flow13/action_set_queue.raw b/fixtures/open_flow13/action_set_queue.raw similarity index 100% rename from features/open_flow13/action_set_queue.raw rename to fixtures/open_flow13/action_set_queue.raw diff --git a/features/open_flow13/apply_actions.raw b/fixtures/open_flow13/apply_actions.raw similarity index 100% rename from features/open_flow13/apply_actions.raw rename to fixtures/open_flow13/apply_actions.raw diff --git a/features/open_flow13/bad_request.raw b/fixtures/open_flow13/bad_request.raw similarity index 100% rename from features/open_flow13/bad_request.raw rename to fixtures/open_flow13/bad_request.raw diff --git a/features/open_flow13/echo_reply_body.raw b/fixtures/open_flow13/echo_reply_body.raw similarity index 100% rename from features/open_flow13/echo_reply_body.raw rename to fixtures/open_flow13/echo_reply_body.raw diff --git a/features/open_flow13/echo_reply_no_body.raw b/fixtures/open_flow13/echo_reply_no_body.raw similarity index 100% rename from features/open_flow13/echo_reply_no_body.raw rename to fixtures/open_flow13/echo_reply_no_body.raw diff --git a/features/open_flow13/echo_request_body.raw b/fixtures/open_flow13/echo_request_body.raw similarity index 100% rename from features/open_flow13/echo_request_body.raw rename to fixtures/open_flow13/echo_request_body.raw diff --git a/features/open_flow13/echo_request_no_body.raw b/fixtures/open_flow13/echo_request_no_body.raw similarity index 100% rename from features/open_flow13/echo_request_no_body.raw rename to fixtures/open_flow13/echo_request_no_body.raw diff --git a/features/open_flow13/features_reply.raw b/fixtures/open_flow13/features_reply.raw similarity index 100% rename from features/open_flow13/features_reply.raw rename to fixtures/open_flow13/features_reply.raw diff --git a/features/open_flow13/features_request.raw b/fixtures/open_flow13/features_request.raw similarity index 100% rename from features/open_flow13/features_request.raw rename to fixtures/open_flow13/features_request.raw diff --git a/features/open_flow13/flow_add_apply_no_match.raw b/fixtures/open_flow13/flow_add_apply_no_match.raw similarity index 100% rename from features/open_flow13/flow_add_apply_no_match.raw rename to fixtures/open_flow13/flow_add_apply_no_match.raw diff --git a/features/open_flow13/flow_mod_add_apply_no_match.raw b/fixtures/open_flow13/flow_mod_add_apply_no_match.raw similarity index 100% rename from features/open_flow13/flow_mod_add_apply_no_match.raw rename to fixtures/open_flow13/flow_mod_add_apply_no_match.raw diff --git a/features/open_flow13/flow_mod_no_match_or_instructions.raw b/fixtures/open_flow13/flow_mod_no_match_or_instructions.raw similarity index 100% rename from features/open_flow13/flow_mod_no_match_or_instructions.raw rename to fixtures/open_flow13/flow_mod_no_match_or_instructions.raw diff --git a/features/open_flow13/hello_failed.raw b/fixtures/open_flow13/hello_failed.raw similarity index 100% rename from features/open_flow13/hello_failed.raw rename to fixtures/open_flow13/hello_failed.raw diff --git a/features/open_flow13/hello_no_version_bitmap.raw b/fixtures/open_flow13/hello_no_version_bitmap.raw similarity index 100% rename from features/open_flow13/hello_no_version_bitmap.raw rename to fixtures/open_flow13/hello_no_version_bitmap.raw diff --git a/features/open_flow13/hello_version_bitmap.raw b/fixtures/open_flow13/hello_version_bitmap.raw similarity index 100% rename from features/open_flow13/hello_version_bitmap.raw rename to fixtures/open_flow13/hello_version_bitmap.raw diff --git a/features/open_flow13/instruction_clear_actions.raw b/fixtures/open_flow13/instruction_clear_actions.raw similarity index 100% rename from features/open_flow13/instruction_clear_actions.raw rename to fixtures/open_flow13/instruction_clear_actions.raw diff --git a/features/open_flow13/instruction_goto_table.raw b/fixtures/open_flow13/instruction_goto_table.raw similarity index 100% rename from features/open_flow13/instruction_goto_table.raw rename to fixtures/open_flow13/instruction_goto_table.raw diff --git a/features/open_flow13/instruction_meter.raw b/fixtures/open_flow13/instruction_meter.raw similarity index 100% rename from features/open_flow13/instruction_meter.raw rename to fixtures/open_flow13/instruction_meter.raw diff --git a/features/open_flow13/instruction_write_actions.raw b/fixtures/open_flow13/instruction_write_actions.raw similarity index 100% rename from features/open_flow13/instruction_write_actions.raw rename to fixtures/open_flow13/instruction_write_actions.raw diff --git a/features/open_flow13/instruction_write_metadata.raw b/fixtures/open_flow13/instruction_write_metadata.raw similarity index 100% rename from features/open_flow13/instruction_write_metadata.raw rename to fixtures/open_flow13/instruction_write_metadata.raw diff --git a/features/open_flow13/oxm_arp_op_field.raw b/fixtures/open_flow13/oxm_arp_op_field.raw similarity index 100% rename from features/open_flow13/oxm_arp_op_field.raw rename to fixtures/open_flow13/oxm_arp_op_field.raw diff --git a/features/open_flow13/oxm_arp_sha_field.raw b/fixtures/open_flow13/oxm_arp_sha_field.raw similarity index 100% rename from features/open_flow13/oxm_arp_sha_field.raw rename to fixtures/open_flow13/oxm_arp_sha_field.raw diff --git a/features/open_flow13/oxm_arp_spa_field.raw b/fixtures/open_flow13/oxm_arp_spa_field.raw similarity index 100% rename from features/open_flow13/oxm_arp_spa_field.raw rename to fixtures/open_flow13/oxm_arp_spa_field.raw diff --git a/features/open_flow13/oxm_arp_tha_field.raw b/fixtures/open_flow13/oxm_arp_tha_field.raw similarity index 100% rename from features/open_flow13/oxm_arp_tha_field.raw rename to fixtures/open_flow13/oxm_arp_tha_field.raw diff --git a/features/open_flow13/oxm_arp_tpa_field.raw b/fixtures/open_flow13/oxm_arp_tpa_field.raw similarity index 100% rename from features/open_flow13/oxm_arp_tpa_field.raw rename to fixtures/open_flow13/oxm_arp_tpa_field.raw diff --git a/features/open_flow13/oxm_ether_destination_field.raw b/fixtures/open_flow13/oxm_ether_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_ether_destination_field.raw rename to fixtures/open_flow13/oxm_ether_destination_field.raw diff --git a/features/open_flow13/oxm_ether_source_field.raw b/fixtures/open_flow13/oxm_ether_source_field.raw similarity index 100% rename from features/open_flow13/oxm_ether_source_field.raw rename to fixtures/open_flow13/oxm_ether_source_field.raw diff --git a/features/open_flow13/oxm_ether_type_field.raw b/fixtures/open_flow13/oxm_ether_type_field.raw similarity index 100% rename from features/open_flow13/oxm_ether_type_field.raw rename to fixtures/open_flow13/oxm_ether_type_field.raw diff --git a/features/open_flow13/oxm_experimenter_stratos_basic_dot11.raw b/fixtures/open_flow13/oxm_experimenter_stratos_basic_dot11.raw similarity index 100% rename from features/open_flow13/oxm_experimenter_stratos_basic_dot11.raw rename to fixtures/open_flow13/oxm_experimenter_stratos_basic_dot11.raw diff --git a/features/open_flow13/oxm_icmpv4_code_field.raw b/fixtures/open_flow13/oxm_icmpv4_code_field.raw similarity index 100% rename from features/open_flow13/oxm_icmpv4_code_field.raw rename to fixtures/open_flow13/oxm_icmpv4_code_field.raw diff --git a/features/open_flow13/oxm_icmpv4_type_field.raw b/fixtures/open_flow13/oxm_icmpv4_type_field.raw similarity index 100% rename from features/open_flow13/oxm_icmpv4_type_field.raw rename to fixtures/open_flow13/oxm_icmpv4_type_field.raw diff --git a/features/open_flow13/oxm_in_phy_port_field.raw b/fixtures/open_flow13/oxm_in_phy_port_field.raw similarity index 100% rename from features/open_flow13/oxm_in_phy_port_field.raw rename to fixtures/open_flow13/oxm_in_phy_port_field.raw diff --git a/features/open_flow13/oxm_in_port_field.raw b/fixtures/open_flow13/oxm_in_port_field.raw similarity index 100% rename from features/open_flow13/oxm_in_port_field.raw rename to fixtures/open_flow13/oxm_in_port_field.raw diff --git a/features/open_flow13/oxm_invalid_field.raw b/fixtures/open_flow13/oxm_invalid_field.raw similarity index 100% rename from features/open_flow13/oxm_invalid_field.raw rename to fixtures/open_flow13/oxm_invalid_field.raw diff --git a/features/open_flow13/oxm_ip_dscp_field.raw b/fixtures/open_flow13/oxm_ip_dscp_field.raw similarity index 100% rename from features/open_flow13/oxm_ip_dscp_field.raw rename to fixtures/open_flow13/oxm_ip_dscp_field.raw diff --git a/features/open_flow13/oxm_ip_ecn_field.raw b/fixtures/open_flow13/oxm_ip_ecn_field.raw similarity index 100% rename from features/open_flow13/oxm_ip_ecn_field.raw rename to fixtures/open_flow13/oxm_ip_ecn_field.raw diff --git a/features/open_flow13/oxm_ipv4_destination_field.raw b/fixtures/open_flow13/oxm_ipv4_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_ipv4_destination_field.raw rename to fixtures/open_flow13/oxm_ipv4_destination_field.raw diff --git a/features/open_flow13/oxm_ipv4_source_field.raw b/fixtures/open_flow13/oxm_ipv4_source_field.raw similarity index 100% rename from features/open_flow13/oxm_ipv4_source_field.raw rename to fixtures/open_flow13/oxm_ipv4_source_field.raw diff --git a/features/open_flow13/oxm_ipv6_destination_field.raw b/fixtures/open_flow13/oxm_ipv6_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_ipv6_destination_field.raw rename to fixtures/open_flow13/oxm_ipv6_destination_field.raw diff --git a/features/open_flow13/oxm_ipv6_source_field.raw b/fixtures/open_flow13/oxm_ipv6_source_field.raw similarity index 100% rename from features/open_flow13/oxm_ipv6_source_field.raw rename to fixtures/open_flow13/oxm_ipv6_source_field.raw diff --git a/features/open_flow13/oxm_masked_arp_sha_field.raw b/fixtures/open_flow13/oxm_masked_arp_sha_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_arp_sha_field.raw rename to fixtures/open_flow13/oxm_masked_arp_sha_field.raw diff --git a/features/open_flow13/oxm_masked_arp_spa_field.raw b/fixtures/open_flow13/oxm_masked_arp_spa_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_arp_spa_field.raw rename to fixtures/open_flow13/oxm_masked_arp_spa_field.raw diff --git a/features/open_flow13/oxm_masked_arp_tha_field.raw b/fixtures/open_flow13/oxm_masked_arp_tha_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_arp_tha_field.raw rename to fixtures/open_flow13/oxm_masked_arp_tha_field.raw diff --git a/features/open_flow13/oxm_masked_arp_tpa_field.raw b/fixtures/open_flow13/oxm_masked_arp_tpa_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_arp_tpa_field.raw rename to fixtures/open_flow13/oxm_masked_arp_tpa_field.raw diff --git a/features/open_flow13/oxm_masked_ether_destination_field.raw b/fixtures/open_flow13/oxm_masked_ether_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_ether_destination_field.raw rename to fixtures/open_flow13/oxm_masked_ether_destination_field.raw diff --git a/features/open_flow13/oxm_masked_ether_source_field.raw b/fixtures/open_flow13/oxm_masked_ether_source_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_ether_source_field.raw rename to fixtures/open_flow13/oxm_masked_ether_source_field.raw diff --git a/features/open_flow13/oxm_masked_ipv4_destination_field.raw b/fixtures/open_flow13/oxm_masked_ipv4_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_ipv4_destination_field.raw rename to fixtures/open_flow13/oxm_masked_ipv4_destination_field.raw diff --git a/features/open_flow13/oxm_masked_ipv4_source_field.raw b/fixtures/open_flow13/oxm_masked_ipv4_source_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_ipv4_source_field.raw rename to fixtures/open_flow13/oxm_masked_ipv4_source_field.raw diff --git a/features/open_flow13/oxm_masked_ipv6_destination_field.raw b/fixtures/open_flow13/oxm_masked_ipv6_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_ipv6_destination_field.raw rename to fixtures/open_flow13/oxm_masked_ipv6_destination_field.raw diff --git a/features/open_flow13/oxm_masked_ipv6_source_field.raw b/fixtures/open_flow13/oxm_masked_ipv6_source_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_ipv6_source_field.raw rename to fixtures/open_flow13/oxm_masked_ipv6_source_field.raw diff --git a/features/open_flow13/oxm_masked_tunnel_id_field.raw b/fixtures/open_flow13/oxm_masked_tunnel_id_field.raw similarity index 100% rename from features/open_flow13/oxm_masked_tunnel_id_field.raw rename to fixtures/open_flow13/oxm_masked_tunnel_id_field.raw diff --git a/features/open_flow13/oxm_metadata_field.raw b/fixtures/open_flow13/oxm_metadata_field.raw similarity index 100% rename from features/open_flow13/oxm_metadata_field.raw rename to fixtures/open_flow13/oxm_metadata_field.raw diff --git a/features/open_flow13/oxm_metadata_masked_field.raw b/fixtures/open_flow13/oxm_metadata_masked_field.raw similarity index 100% rename from features/open_flow13/oxm_metadata_masked_field.raw rename to fixtures/open_flow13/oxm_metadata_masked_field.raw diff --git a/features/open_flow13/oxm_no_fields.raw b/fixtures/open_flow13/oxm_no_fields.raw similarity index 100% rename from features/open_flow13/oxm_no_fields.raw rename to fixtures/open_flow13/oxm_no_fields.raw diff --git a/features/open_flow13/oxm_sctp_destination_field.raw b/fixtures/open_flow13/oxm_sctp_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_sctp_destination_field.raw rename to fixtures/open_flow13/oxm_sctp_destination_field.raw diff --git a/features/open_flow13/oxm_sctp_source_field.raw b/fixtures/open_flow13/oxm_sctp_source_field.raw similarity index 100% rename from features/open_flow13/oxm_sctp_source_field.raw rename to fixtures/open_flow13/oxm_sctp_source_field.raw diff --git a/features/open_flow13/oxm_tcp_destination_field.raw b/fixtures/open_flow13/oxm_tcp_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_tcp_destination_field.raw rename to fixtures/open_flow13/oxm_tcp_destination_field.raw diff --git a/features/open_flow13/oxm_tcp_field.raw b/fixtures/open_flow13/oxm_tcp_field.raw similarity index 100% rename from features/open_flow13/oxm_tcp_field.raw rename to fixtures/open_flow13/oxm_tcp_field.raw diff --git a/features/open_flow13/oxm_tcp_source_field.raw b/fixtures/open_flow13/oxm_tcp_source_field.raw similarity index 100% rename from features/open_flow13/oxm_tcp_source_field.raw rename to fixtures/open_flow13/oxm_tcp_source_field.raw diff --git a/features/open_flow13/oxm_tunnel_id_field.raw b/fixtures/open_flow13/oxm_tunnel_id_field.raw similarity index 100% rename from features/open_flow13/oxm_tunnel_id_field.raw rename to fixtures/open_flow13/oxm_tunnel_id_field.raw diff --git a/features/open_flow13/oxm_udp_destination_field.raw b/fixtures/open_flow13/oxm_udp_destination_field.raw similarity index 100% rename from features/open_flow13/oxm_udp_destination_field.raw rename to fixtures/open_flow13/oxm_udp_destination_field.raw diff --git a/features/open_flow13/oxm_udp_field.raw b/fixtures/open_flow13/oxm_udp_field.raw similarity index 100% rename from features/open_flow13/oxm_udp_field.raw rename to fixtures/open_flow13/oxm_udp_field.raw diff --git a/features/open_flow13/oxm_udp_source_field.raw b/fixtures/open_flow13/oxm_udp_source_field.raw similarity index 100% rename from features/open_flow13/oxm_udp_source_field.raw rename to fixtures/open_flow13/oxm_udp_source_field.raw diff --git a/features/open_flow13/oxm_vlan_pcp_field.raw b/fixtures/open_flow13/oxm_vlan_pcp_field.raw similarity index 100% rename from features/open_flow13/oxm_vlan_pcp_field.raw rename to fixtures/open_flow13/oxm_vlan_pcp_field.raw diff --git a/features/open_flow13/oxm_vlan_vid_field.raw b/fixtures/open_flow13/oxm_vlan_vid_field.raw similarity index 100% rename from features/open_flow13/oxm_vlan_vid_field.raw rename to fixtures/open_flow13/oxm_vlan_vid_field.raw diff --git a/features/open_flow13/packet_in.raw b/fixtures/open_flow13/packet_in.raw similarity index 100% rename from features/open_flow13/packet_in.raw rename to fixtures/open_flow13/packet_in.raw diff --git a/features/open_flow13/packet_out.raw b/fixtures/open_flow13/packet_out.raw similarity index 100% rename from features/open_flow13/packet_out.raw rename to fixtures/open_flow13/packet_out.raw diff --git a/features/open_flow13/send_out_port.raw b/fixtures/open_flow13/send_out_port.raw similarity index 100% rename from features/open_flow13/send_out_port.raw rename to fixtures/open_flow13/send_out_port.raw diff --git a/features/open_flow13/table_stats_reply.raw b/fixtures/open_flow13/table_stats_reply.raw similarity index 100% rename from features/open_flow13/table_stats_reply.raw rename to fixtures/open_flow13/table_stats_reply.raw diff --git a/features/open_flow13/table_stats_request.raw b/fixtures/open_flow13/table_stats_request.raw similarity index 100% rename from features/open_flow13/table_stats_request.raw rename to fixtures/open_flow13/table_stats_request.raw diff --git a/features/udp_no_payload.raw b/fixtures/udp_no_payload.raw similarity index 100% rename from features/udp_no_payload.raw rename to fixtures/udp_no_payload.raw diff --git a/features/udp_with_payload.raw b/fixtures/udp_with_payload.raw similarity index 100% rename from features/udp_with_payload.raw rename to fixtures/udp_with_payload.raw diff --git a/lib/pio.rb b/lib/pio.rb index 951f7078..1cc0091c 100644 --- a/lib/pio.rb +++ b/lib/pio.rb @@ -5,5 +5,5 @@ require 'pio/icmp' require 'pio/lldp' require 'pio/mac' -require 'pio/open_flow' require 'pio/udp' +require 'pio/open_flow' diff --git a/lib/pio/arp.rb b/lib/pio/arp.rb index 312ef4a3..f4f41046 100644 --- a/lib/pio/arp.rb +++ b/lib/pio/arp.rb @@ -1,14 +1,17 @@ require 'pio/arp/format' -require 'pio/arp/request' require 'pio/arp/reply' -require 'pio/message_type_selector' +require 'pio/arp/request' +require 'pio/parse_error' -# Packet parser and generator library. module Pio # ARP parser and generator. class Arp - extend MessageTypeSelector - message_type Request::OPERATION => Request, Reply::OPERATION => Reply + def self.read(raw_data) + format = Format.read(raw_data) + { Request.operation => Request, + Reply.operation => Reply }.fetch(format.operation).create(format) + rescue + raise Pio::ParseError, $ERROR_INFO.message + end end - ARP = Arp end diff --git a/lib/pio/arp/format.rb b/lib/pio/arp/format.rb index 0911231f..46e211c2 100644 --- a/lib/pio/arp/format.rb +++ b/lib/pio/arp/format.rb @@ -8,11 +8,11 @@ module Pio class Arp # ARP parser. class Format < BinData::Record - include EthernetHeader + include Ethernet endian :big - ethernet_header ether_type: EtherType::ARP + ethernet_header ether_type: Ethernet::Type::ARP uint16 :hardware_type, value: 1 uint16 :protocol_type, value: 0x0800 uint8 :hardware_length, value: 6 @@ -23,17 +23,13 @@ class Format < BinData::Record mac_address :target_hardware_address ip_address :target_protocol_address - def message_type - operation - end - # rubocop:disable MethodLength def to_exact_match(in_port) match_options = { in_port: in_port, source_mac_address: source_mac, destination_mac_address: destination_mac, - vlan_vid: vlan_vid, + vlan_vid: 0xffff, vlan_priority: vlan_pcp, ether_type: ether_type, tos: 0, diff --git a/lib/pio/arp/message.rb b/lib/pio/arp/message.rb index 74a5daab..2070a0b1 100644 --- a/lib/pio/arp/message.rb +++ b/lib/pio/arp/message.rb @@ -1,14 +1,18 @@ require 'pio/arp/format' +require 'pio/message' module Pio class Arp # Base class of ARP Request and Reply - class Message - private_class_method :new + class Message < Pio::Message + def self.create(format) + allocate.tap do |message| + message.instance_variable_set :@format, format + end + end def initialize(user_options) - options = self.class.const_get(:Options).new(user_options.dup.freeze) - @format = Arp::Format.new(options.to_hash) + @format = Arp::Format.new(parse_options(user_options)) end def method_missing(method, *args) diff --git a/lib/pio/arp/reply.rb b/lib/pio/arp/reply.rb index 30b4c43e..5650b820 100644 --- a/lib/pio/arp/reply.rb +++ b/lib/pio/arp/reply.rb @@ -1,43 +1,20 @@ require 'pio/arp/message' +require 'pio/instance_inspector' require 'pio/mac' -require 'pio/options' module Pio class Arp # ARP Reply packet generator class Reply < Message - OPERATION = 2 - public_class_method :new + include InstanceInspector - # User options for creating an Arp Reply. - class Options < Pio::Options - mandatory_option :source_mac - mandatory_option :destination_mac - mandatory_option :sender_protocol_address - mandatory_option :target_protocol_address - - def initialize(options) - validate options - @source_mac = Mac.new(options[:source_mac]).freeze - @destination_mac = Mac.new(options[:destination_mac]).freeze - @sender_protocol_address = - IPv4Address.new(options[:sender_protocol_address]).freeze - @target_protocol_address = - IPv4Address.new(options[:target_protocol_address]).freeze - end - - def to_hash - { - operation: OPERATION, - source_mac: @source_mac, - destination_mac: @destination_mac, - sender_hardware_address: @source_mac, - target_hardware_address: @destination_mac, - sender_protocol_address: @sender_protocol_address, - target_protocol_address: @target_protocol_address - }.freeze - end - end + option :operation, value: 2 + option :source_mac + option :destination_mac + option :sender_hardware_address, value: :source_mac + option :target_hardware_address, value: :destination_mac + option :sender_protocol_address + option :target_protocol_address end end end diff --git a/lib/pio/arp/request.rb b/lib/pio/arp/request.rb index 0d240867..d1ae4dae 100644 --- a/lib/pio/arp/request.rb +++ b/lib/pio/arp/request.rb @@ -1,44 +1,20 @@ require 'pio/arp/message' +require 'pio/instance_inspector' require 'pio/mac' -require 'pio/options' module Pio class Arp # ARP Request packet generator class Request < Message - OPERATION = 1 - public_class_method :new + include InstanceInspector - # User options for creating an Arp Request. - class Options < Pio::Options - mandatory_option :source_mac - mandatory_option :sender_protocol_address - mandatory_option :target_protocol_address - - BROADCAST_MAC = Mac.new(0xffffffffffff).freeze - ALL_ZERO_MAC = Mac.new(0).freeze - - def initialize(options) - validate options - @source_mac = Mac.new(options[:source_mac]).freeze - @sender_protocol_address = - IPv4Address.new(options[:sender_protocol_address]).freeze - @target_protocol_address = - IPv4Address.new(options[:target_protocol_address]).freeze - end - - def to_hash - { - operation: OPERATION, - source_mac: @source_mac, - destination_mac: BROADCAST_MAC, - sender_hardware_address: @source_mac, - target_hardware_address: ALL_ZERO_MAC, - sender_protocol_address: @sender_protocol_address, - target_protocol_address: @target_protocol_address - }.freeze - end - end + option :operation, value: 1 + option :source_mac + option :destination_mac, default: 'ff:ff:ff:ff:ff:ff'.freeze + option :sender_hardware_address, value: :source_mac + option :target_hardware_address, default: '00:00:00:00:00:00'.freeze + option :sender_protocol_address + option :target_protocol_address end end end diff --git a/lib/pio/class_inspector.rb b/lib/pio/class_inspector.rb new file mode 100644 index 00000000..994e4265 --- /dev/null +++ b/lib/pio/class_inspector.rb @@ -0,0 +1,18 @@ +require 'active_support/core_ext/string/inflections' + +module Pio + # Introduces Class.inspect method + module ClassInspector + # rubocop:disable LineLength + def inspect + field_and_type = fields.each_with_object([]) do |each, result| + next if each.name == :padding + result << [each.name, + each.prototype.instance_variable_get(:@obj_class).name.demodulize.sub(/be$/, '').underscore] + end + signature = field_and_type.map { |field, type| "#{field}: #{type}" }.join(', ') + "#{self}(#{signature})" + end + # rubocop:enable LineLength + end +end diff --git a/lib/pio/dhcp.rb b/lib/pio/dhcp.rb index 48761e44..7b5dcc08 100644 --- a/lib/pio/dhcp.rb +++ b/lib/pio/dhcp.rb @@ -22,7 +22,7 @@ class Dhcp ROUTER_TLV, DNS_TLV, NTP_SERVERS_TLV - ] + ].freeze end DHCP = Dhcp end @@ -41,7 +41,7 @@ class Dhcp Offer::TYPE => Offer, Request::TYPE => Request, Ack::TYPE => Ack - } + }.freeze def self.read(raw_data) begin diff --git a/lib/pio/dhcp/frame.rb b/lib/pio/dhcp/frame.rb index 1241179c..b8b451c2 100644 --- a/lib/pio/dhcp/frame.rb +++ b/lib/pio/dhcp/frame.rb @@ -12,12 +12,12 @@ class Frame < BinData::Record OPTION_FIELD_LENGTH = 60 - include EthernetHeader - include IPv4Header + include Ethernet + include IPv4 include UdpHeader endian :big - ethernet_header ether_type: EtherType::IPV4 + ethernet_header ether_type: Ethernet::Type::IPV4 ipv4_header ip_protocol: ProtocolNumber::UDP udp_header dhcp_field :dhcp @@ -31,7 +31,7 @@ def to_binary def ff_and_padding padding_length = OPTION_FIELD_LENGTH - dhcp.optional_tlvs.num_bytes - 1 - "\xFF" + (padding_length > 0 ? "\x00" * padding_length : '') + [0xFF].pack('C') + (padding_length > 0 ? "\x00" * padding_length : '') end end end diff --git a/lib/pio/dhcp/optional_tlv.rb b/lib/pio/dhcp/optional_tlv.rb index 0fe443cb..90a4491f 100644 --- a/lib/pio/dhcp/optional_tlv.rb +++ b/lib/pio/dhcp/optional_tlv.rb @@ -7,15 +7,15 @@ module Pio class Dhcp # DHCP Optional TLV class OptionalTlv < BinData::Record - DEFAULT = 'default' + DEFAULT = 'default'.freeze endian :big bit8 :tlv_type bit8 :tlv_info_length, - onlyif: -> { !(end_of_dhcpdu?) } + onlyif: -> { !end_of_dhcpdu? } choice :tlv_value, - onlyif: -> { !(end_of_dhcpdu?) }, + onlyif: -> { !end_of_dhcpdu? }, selection: :chooser do uint8 Dhcp::MESSAGE_TYPE_TLV ip_address Dhcp::SERVER_IDENTIFIER_TLV diff --git a/lib/pio/ethernet_frame.rb b/lib/pio/ethernet_frame.rb new file mode 100644 index 00000000..cef4fde3 --- /dev/null +++ b/lib/pio/ethernet_frame.rb @@ -0,0 +1,20 @@ +require 'bindata' +require 'pio/ethernet_header' +require 'pio/class_inspector' +require 'pio/instance_inspector' +require 'pio/ruby_dumper' + +module Pio + # Ethernet frame parser + class EthernetFrame < BinData::Record + extend ClassInspector + include Ethernet + include InstanceInspector + include RubyDumper + + endian :big + + ethernet_header + rest :rest + end +end diff --git a/lib/pio/ethernet_header.rb b/lib/pio/ethernet_header.rb index a580e1a8..e66d6ca7 100644 --- a/lib/pio/ethernet_header.rb +++ b/lib/pio/ethernet_header.rb @@ -1,10 +1,16 @@ +require 'pio/class_inspector' +require 'pio/instance_inspector' +require 'pio/ruby_dumper' +require 'pio/type/ether_type' require 'pio/type/mac_address' module Pio # Adds ethernet_header macro. - module EthernetHeader - # EtherType constants for ethernet_header.ether_type. - module EtherType + module Ethernet + MINIMUM_FRAME_SIZE = 64 + + # EtherType constants + module Type ARP = 0x0806 IPV4 = 0x0800 VLAN = 0x8100 @@ -12,28 +18,44 @@ module EtherType end # This method smells of :reek:TooManyStatements + # rubocop:disable MethodLength def self.included(klass) - def klass.ethernet_header(options) + def klass.ethernet_header(options = nil) mac_address :destination_mac mac_address :source_mac - uint16 :ether_type, value: options.fetch(:ether_type) - bit3 :vlan_pcp_internal, onlyif: :vlan? + if options + ether_type :ether_type, value: options.fetch(:ether_type) + else + ether_type :ether_type + end + bit3 :vlan_pcp, onlyif: :vlan? bit1 :vlan_cfi, onlyif: :vlan? - bit12 :vlan_vid_internal, onlyif: :vlan? + bit12 :vlan_vid, onlyif: :vlan? uint16 :ether_type_vlan, value: :ether_type, onlyif: :vlan? end end + # rubocop:enable MethodLength - def vlan_vid - vlan? ? vlan_vid_internal : 0xffff + def ethernet_header_length + vlan? ? 18 : 14 end - def vlan_pcp - vlan? ? vlan_pcp_internal : 0 - end + private def vlan? - ether_type == EtherType::VLAN + ether_type == Type::VLAN end end + + # Ethernet header generator/parser + class EthernetHeader < BinData::Record + extend ClassInspector + include Ethernet + include InstanceInspector + include RubyDumper + + endian :big + + ethernet_header + end end diff --git a/lib/pio/icmp.rb b/lib/pio/icmp.rb index 01278460..ed4b7b21 100644 --- a/lib/pio/icmp.rb +++ b/lib/pio/icmp.rb @@ -1,14 +1,17 @@ require 'pio/icmp/format' require 'pio/icmp/reply' require 'pio/icmp/request' -require 'pio/message_type_selector' +require 'pio/parse_error' -# Packet parser and generator library. module Pio # Icmp parser and generator. class Icmp - extend MessageTypeSelector - message_type Request::TYPE => Request, Reply::TYPE => Reply + def self.read(raw_data) + format = Format.read(raw_data) + { Request.icmp_type => Request, + Reply.icmp_type => Reply }.fetch(format.icmp_type).create(format) + rescue + raise Pio::ParseError, $ERROR_INFO.message + end end - ICMP = Icmp end diff --git a/lib/pio/icmp/format.rb b/lib/pio/icmp/format.rb index 11a8d342..139140fa 100644 --- a/lib/pio/icmp/format.rb +++ b/lib/pio/icmp/format.rb @@ -1,37 +1,33 @@ -require 'bindata' require 'pio/ethernet_header' require 'pio/ipv4_header' +require 'pio/monkey_patch/bindata_record' module Pio - # Icmp parser and generator. + # ICMP parser and generator class Icmp - # Icmp parser. + # ICMP format class Format < BinData::Record - MINIMUM_IP_PACKET_LENGTH = 50 - - include EthernetHeader - include IPv4Header + include Ethernet + include IPv4 endian :big - ethernet_header ether_type: EtherType::IPV4 + ethernet_header ether_type: Ethernet::Type::IPV4 ipv4_header ip_protocol: ProtocolNumber::ICMP uint8 :icmp_type uint8 :icmp_code, initial_value: 0 uint16 :icmp_checksum, value: :calculate_icmp_checksum uint16 :icmp_identifier uint16 :icmp_sequence_number - string :echo_data, read_length: :echo_data_read_length - string :padding, read_length: 0, initial_value: :icmp_padding_length - - def message_type - icmp_type - end - - alias_method :to_binary, :to_binary_s + string :echo_data, read_length: :echo_data_length + string :padding, length: :padding_length private + def icmp_header_length + 8 + end + def calculate_icmp_checksum sum = [icmp_type * 0x100 + icmp_code, icmp_identifier, @@ -40,14 +36,15 @@ def calculate_icmp_checksum ~((sum & 0xffff) + (sum >> 16)) & 0xffff end - def echo_data_read_length - ip_total_length - (ip_header_length * 4) - 8 + def echo_data_length + ip_total_length - ip_header_length_in_bytes - icmp_header_length end - def icmp_padding_length - length = MINIMUM_IP_PACKET_LENGTH - - (ip_header_length * 4) - 8 - echo_data.length - length > 0 ? "\x00" * length : '' + def padding_length + tmp = Ethernet::MINIMUM_FRAME_SIZE - + ethernet_header_length - ip_header_length_in_bytes - + icmp_header_length - echo_data.length + tmp > 0 ? tmp : 0 end end end diff --git a/lib/pio/icmp/message.rb b/lib/pio/icmp/message.rb index 6cc94f03..fe05e227 100644 --- a/lib/pio/icmp/message.rb +++ b/lib/pio/icmp/message.rb @@ -1,14 +1,28 @@ +require 'pio/class_inspector' require 'pio/icmp/format' +require 'pio/instance_inspector' +require 'pio/message' +require 'pio/ruby_dumper' module Pio class Icmp # Base class of Icmp::Request and Icmp::Reply. - class Message - private_class_method :new + class Message < Pio::Message + extend ClassInspector + include InstanceInspector + + def self.fields + Icmp::Format.fields + end + + def self.create(format) + allocate.tap do |message| + message.instance_variable_set :@format, format + end + end def initialize(user_options) - options = self.class.const_get(:Options).new(user_options) - @format = Icmp::Format.new(options.to_hash) + @format = Icmp::Format.new(parse_options(user_options)) end def method_missing(method, *args) diff --git a/lib/pio/icmp/options.rb b/lib/pio/icmp/options.rb deleted file mode 100644 index 8ae9632d..00000000 --- a/lib/pio/icmp/options.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'pio/options' - -module Pio - class Icmp - # User options for creating an ICMP messages. - class Options < Pio::Options - def to_hash - { - icmp_type: @type, - source_mac: @source_mac, - destination_mac: @destination_mac, - source_ip_address: @source_ip_address, - destination_ip_address: @destination_ip_address, - icmp_identifier: @identifier, - icmp_sequence_number: @sequence_number, - echo_data: @echo_data - }.freeze - end - end - end -end diff --git a/lib/pio/icmp/reply.rb b/lib/pio/icmp/reply.rb index 79954e6c..71693c90 100644 --- a/lib/pio/icmp/reply.rb +++ b/lib/pio/icmp/reply.rb @@ -1,43 +1,17 @@ require 'pio/icmp/message' -require 'pio/icmp/options' -require 'pio/mac' module Pio class Icmp # ICMP Reply packet generator class Reply < Message - TYPE = 0 - public_class_method :new - - # User options for creating an ICMP Reply. - class Options < Pio::Icmp::Options - mandatory_option :source_mac - mandatory_option :destination_mac - mandatory_option :source_ip_address - mandatory_option :destination_ip_address - mandatory_option :identifier - mandatory_option :sequence_number - option :echo_data - - # rubocop:disable MethodLength - # rubocop:disable AbcSize - def initialize(options) - validate options - @type = TYPE - - @source_mac = Mac.new(options[:source_mac]).freeze - @destination_mac = Mac.new(options[:destination_mac]).freeze - @source_ip_address = - IPv4Address.new(options[:source_ip_address]).freeze - @destination_ip_address = - IPv4Address.new(options[:destination_ip_address]).freeze - @identifier = options[:identifier] - @sequence_number = options[:sequence_number] - @echo_data = options[:echo_data] || '' - end - # rubocop:enable AbcSize - # rubocop:enable MethodLength - end + option :icmp_type, value: 0 + option :source_mac + option :destination_mac + option :source_ip_address + option :destination_ip_address + option :icmp_identifier + option :icmp_sequence_number + option :echo_data, default: '' end end end diff --git a/lib/pio/icmp/request.rb b/lib/pio/icmp/request.rb index cf8fa8dc..2298165e 100644 --- a/lib/pio/icmp/request.rb +++ b/lib/pio/icmp/request.rb @@ -1,52 +1,17 @@ require 'pio/icmp/message' -require 'pio/icmp/options' -require 'pio/mac' module Pio class Icmp # ICMP Request packet generator class Request < Message - TYPE = 8 - public_class_method :new - - # User options for creating an ICMP Request. - class Options < Pio::Icmp::Options - DEFAULT_IDENTIFIER = 0x0100 - DEFAULT_SEQUENCE_NUMBER = 0 - - mandatory_option :source_mac - mandatory_option :destination_mac - mandatory_option :source_ip_address - mandatory_option :destination_ip_address - option :identifier - option :sequence_number - option :echo_data - - # rubocop:disable MethodLength - # rubocop:disable AbcSize - def initialize(options) - validate options - @type = TYPE - - @source_mac = Mac.new(options[:source_mac]).freeze - @destination_mac = Mac.new(options[:destination_mac]).freeze - @source_ip_address = - IPv4Address.new(options[:source_ip_address]).freeze - @destination_ip_address = - IPv4Address.new(options[:destination_ip_address]).freeze - @identifier = - options[:icmp_identifier] || - options[:identifier] || - DEFAULT_IDENTIFIER - @sequence_number = - options[:icmp_sequence_number] || - options[:sequence_number] || - DEFAULT_SEQUENCE_NUMBER - @echo_data = options[:echo_data] || '' - end - # rubocop:enable AbcSize - # rubocop:enable MethodLength - end + option :icmp_type, value: 8 + option :source_mac + option :destination_mac + option :source_ip_address + option :destination_ip_address + option :icmp_identifier, default: 0 + option :icmp_sequence_number, default: 0 + option :echo_data, default: '' end end end diff --git a/lib/pio/instance_inspector.rb b/lib/pio/instance_inspector.rb new file mode 100644 index 00000000..48d9f0b0 --- /dev/null +++ b/lib/pio/instance_inspector.rb @@ -0,0 +1,14 @@ +module Pio + # Introduces #inspect method + module InstanceInspector + def inspect + "#<#{self.class.name} " + + field_names.map do |each| + next if each == :padding + next unless __send__("#{each}?") + "#{each}: #{__send__(each).inspect}" + end.compact.join(', ') + + '>' + end + end +end diff --git a/lib/pio/ipv4_address.rb b/lib/pio/ipv4_address.rb index 74490050..ba16bd88 100644 --- a/lib/pio/ipv4_address.rb +++ b/lib/pio/ipv4_address.rb @@ -21,14 +21,14 @@ class IPv4Address # @return [IPv4Address] self # a proxy to IPAddr. def initialize(addr) - case addr - when Integer - @value = IPAddr.new(addr, Socket::AF_INET) - when String - @value = IPAddr.new(addr) - else - @value = addr.value - end + @value = case addr + when Integer + IPAddr.new(addr, Socket::AF_INET) + when String + IPAddr.new(addr) + else + addr.value + end end # @return [String] the IPv4 address in its text representation. @@ -78,14 +78,14 @@ def mask!(masklen) @value = @value.mask(masklen) self end - alias_method :prefix!, :mask! + alias prefix! mask! # Returns the IPv4 address masked with +masklen+. # @return [IPv4Address] def mask(masklen) clone.mask!(masklen) end - alias_method :prefix, :mask + alias prefix mask # @return [bool] # Returns true if the address belongs to class A. @@ -110,7 +110,7 @@ def class_c? def class_d? mask(4).to_s == '224.0.0.0' end - alias_method :multicast?, :class_d? + alias multicast? class_d? # @return [bool] # Returns true if the address belongs to class E. diff --git a/lib/pio/ipv4_header.rb b/lib/pio/ipv4_header.rb index 2e9e786e..4d44c009 100644 --- a/lib/pio/ipv4_header.rb +++ b/lib/pio/ipv4_header.rb @@ -1,9 +1,12 @@ +require 'pio/instance_inspector' +require 'pio/monkey_patch/bindata_string' require 'pio/payload' +require 'pio/ruby_dumper' require 'pio/type/ip_address' module Pio # IP Version 4 Header Format - module IPv4Header + module IPv4 # Internet protocol numbers for ipv4_header.ip_protocol module ProtocolNumber ICMP = 1 @@ -39,7 +42,7 @@ def to_exact_match(in_port) in_port: in_port, source_mac_address: source_mac, destination_mac_address: destination_mac, - vlan_vid: vlan_vid, + vlan_vid: vlan? ? vlan_vid : 0xffff, vlan_priority: vlan_pcp, ether_type: ether_type, tos: ip_type_of_service, @@ -53,6 +56,10 @@ def to_exact_match(in_port) end # rubocop:enable MethodLength + def ip_header_length_in_bytes + ip_header_length * 4 + end + private def calculate_ip_length @@ -82,4 +89,19 @@ def ip_option_length 20 - ip_header_length * 4 end end + + # IPv4 header generator/parser + class IPv4Header < BinData::Record + include IPv4 + include InstanceInspector + include RubyDumper + + ipv4_header + + # rubocop:disable LineLength + def self.inspect + 'IPv4Header(ip_version: bit4, ip_header_length: bit4, ip_type_of_service: uint8, ip_total_length: uint16, ip_identifier: uint16, ip_flag: bit3, ip_fragment: bit13, ip_ttl: uint8, ip_protocol: uint8, ip_header_checksum: uint16, source_ip_address: ip_address, destination_ip_address: ip_address, ip_option: string)' + end + # rubocop:enable LineLength + end end diff --git a/lib/pio/lldp/frame.rb b/lib/pio/lldp/frame.rb index 8038a916..7909c8c7 100644 --- a/lib/pio/lldp/frame.rb +++ b/lib/pio/lldp/frame.rb @@ -11,11 +11,11 @@ module Pio class Lldp # LLDP frame class Frame < BinData::Record - include EthernetHeader + include Ethernet endian :big - ethernet_header ether_type: EtherType::LLDP + ethernet_header ether_type: Ethernet::Type::LLDP chassis_id_tlv :chassis_id port_id_tlv :port_id ttl_tlv :ttl, initial_value: 120 diff --git a/lib/pio/lldp/optional_tlv.rb b/lib/pio/lldp/optional_tlv.rb index 842f3041..8526db9d 100644 --- a/lib/pio/lldp/optional_tlv.rb +++ b/lib/pio/lldp/optional_tlv.rb @@ -29,7 +29,7 @@ class OptionalTlv < BinData::Record end def end_of_lldpdu? - tlv_type == 0 + tlv_type.zero? end def chooser @@ -52,7 +52,7 @@ def optional_tlv? end def end_of_lldpdu_tlv? - tlv_type == 0 + tlv_type.zero? end end end diff --git a/lib/pio/mac.rb b/lib/pio/mac.rb index b4a2fe5a..430ccdcc 100644 --- a/lib/pio/mac.rb +++ b/lib/pio/mac.rb @@ -29,7 +29,7 @@ def initialize(value) @value = value.to_int validate_value_range else - fail TypeError + raise TypeError end rescue ArgumentError, TypeError raise InvalidValueError, "Invalid MAC address: #{value.inspect}" @@ -91,6 +91,10 @@ def to_a to_s.split(':').map(&:hex) end + def to_hex + to_a.map(&:to_hex).join(', ') + end + # @!endgroup # @!group Predicates @@ -202,12 +206,12 @@ def parse_mac_string(mac) when /^(?:#{octet}(:)){5}#{octet}$/, /^(?:#{doctet}(\.)){2}#{doctet}$/ mac.gsub(Regexp.last_match[1], '').hex else - fail ArgumentError + raise ArgumentError end end def validate_value_range - fail ArgumentError unless @value >= 0 && @value <= 0xffffffffffff + raise ArgumentError unless @value >= 0 && @value <= 0xffffffffffff end end end diff --git a/lib/pio/message.rb b/lib/pio/message.rb new file mode 100644 index 00000000..c9b596a5 --- /dev/null +++ b/lib/pio/message.rb @@ -0,0 +1,50 @@ +require 'active_support/core_ext/class/attribute' +require 'pio/ruby_dumper' + +module Pio + # Base message class + class Message + include RubyDumper + + class_attribute :options + + def self.option(name, value: nil, default: nil) + self.options ||= {} + if value && !value.is_a?(Symbol) + class_eval { class_attribute name } + class_eval { __send__("#{name}=", value) } + end + self.options.merge! name => { value: value, default: default } + end + + private + + # rubocop:disable MethodLength + # rubocop:disable AbcSize + # rubocop:disable PerceivedComplexity + # rubocop:disable CyclomaticComplexity + def parse_options(user_options) + options = {} + self.class.options.each_pair do |key, attrs| + if !attrs[:value] && !attrs[:default] + begin + options[key] = user_options.fetch(key) + rescue + raise "#{key} option is a mandatory" + end + elsif attrs[:value] && attrs[:value].is_a?(Symbol) + options[key] = user_options.fetch(attrs[:value]) + elsif attrs[:value] + options[key] = user_options[key] || attrs[:value] + else + options[key] = user_options[key] || attrs[:default] + end + end + options + end + # rubocop:enable MethodLength + # rubocop:enable AbcSize + # rubocop:enable PerceivedComplexity + # rubocop:enable CyclomaticComplexity + end +end diff --git a/lib/pio/monkey_patch/bindata_record.rb b/lib/pio/monkey_patch/bindata_record.rb new file mode 100644 index 00000000..44fe52d6 --- /dev/null +++ b/lib/pio/monkey_patch/bindata_record.rb @@ -0,0 +1,6 @@ +module BinData + # Add BinData::Record#to_binary + class Record + alias to_binary to_binary_s + end +end diff --git a/lib/pio/monkey_patch/bindata_string.rb b/lib/pio/monkey_patch/bindata_string.rb new file mode 100644 index 00000000..2ee54820 --- /dev/null +++ b/lib/pio/monkey_patch/bindata_string.rb @@ -0,0 +1,10 @@ +module BinData + # Add BinData::String#to_bytes + class String + def to_bytes + to_s.unpack('H*').pop.scan(/[0-9a-f]{2}/).map do |each| + "0x#{each}" + end.join(', ') + end + end +end diff --git a/lib/pio/monkey_patch/integer/base_conversions.rb b/lib/pio/monkey_patch/integer/base_conversions.rb index 6782bd52..8d60f5cc 100644 --- a/lib/pio/monkey_patch/integer/base_conversions.rb +++ b/lib/pio/monkey_patch/integer/base_conversions.rb @@ -3,7 +3,7 @@ module Integer # to_hex etc. module BaseConversions def to_hex - format '%#x', self + format '0x%02x', self end end end diff --git a/lib/pio/monkey_patch/uint.rb b/lib/pio/monkey_patch/uint.rb new file mode 100644 index 00000000..9673cfb9 --- /dev/null +++ b/lib/pio/monkey_patch/uint.rb @@ -0,0 +1,9 @@ +require 'bindata' +require 'pio/monkey_patch/uint/base_conversions' + +# BinData's base module +module BinData + Uint8.__send__ :include, MonkeyPatch::Uint::BaseConversions + Uint16be.__send__ :include, MonkeyPatch::Uint::BaseConversions + Uint32be.__send__ :include, MonkeyPatch::Uint::BaseConversions +end diff --git a/lib/pio/monkey_patch/uint/base_conversions.rb b/lib/pio/monkey_patch/uint/base_conversions.rb new file mode 100644 index 00000000..e28132ed --- /dev/null +++ b/lib/pio/monkey_patch/uint/base_conversions.rb @@ -0,0 +1,14 @@ +module MonkeyPatch + module Uint + # Uint8#to_bytes, Uintxxbe#to_bytes + module BaseConversions + def to_bytes + /Uint(8|\d+be)$/=~ self.class.name + nbyte = Regexp.last_match(1).to_i / 4 + format("%0#{nbyte}x", to_i).scan(/.{1,2}/).map do |each| + '0x' + each + end.join(', ') + end + end + end +end diff --git a/lib/pio/open_flow.rb b/lib/pio/open_flow.rb index 65b62649..841cc0d4 100644 --- a/lib/pio/open_flow.rb +++ b/lib/pio/open_flow.rb @@ -1,67 +1,53 @@ -require 'pio/open_flow/datapath_id' -require 'pio/open_flow/error' -require 'pio/open_flow/flags' -require 'pio/open_flow/message' -require 'pio/open_flow/open_flow_header' -require 'pio/open_flow10' -require 'pio/open_flow13' +require 'active_support/core_ext/array/access' +require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/core_ext/module/introspection' +require 'pio/open_flow/header' +require 'pio/open_flow/parser' module Pio # Common OpenFlow modules/classes. module OpenFlow - def self.version - fail unless @version - @version - end + mattr_reader :version, instance_reader: false - def self.switch_version(version) - [:Barrier, :Echo, :Features, :FlowMod, :Hello, :Match, - :PacketIn, :FlowRemoved, :PacketOut, :SendOutPort, - :SetSourceMacAddress, :SetDestinationMacAddress, :PortStatus, :Stats, - :FlowStats, :DescriptionStats, :AggregateStats, :TableStats, :PortStats, - :QueueStats, :Error, :SetArpOperation, :SetArpSenderProtocolAddress, - :SetArpSenderHardwareAddress, :NiciraRegMove, :SetMetadata, - :NiciraRegLoad, :NiciraSendOutPort].each do |each| - set_message_class_name each, version - @version = version.to_s + def self.version=(version) + return if OpenFlow.version == version.to_sym + find_all_class_by_version(version).each do |each| + alias_open_flow_class each end + @@version = version.to_sym # rubocop:disable ClassVars + rescue NameError + raise "#{version} is not supported yet." end - # rubocop:disable MethodLength def self.read(binary) - parser = { - 0 => Pio::Hello, - 1 => Pio::OpenFlow::Error, - 2 => Pio::Echo::Request, - 3 => Pio::Echo::Reply, - 5 => Pio::Features::Request, - 6 => Pio::Features::Reply, - 10 => Pio::PacketIn, - 11 => Pio::FlowRemoved, - 12 => Pio::PortStatus, - 13 => Pio::PacketOut, - 14 => Pio::FlowMod, - 16 => Pio::Stats::Request, - 17 => Pio::Stats::Reply, - 18 => Pio::Barrier::Request, - 19 => Pio::Barrier::Reply - } - header = OpenFlowHeaderParser.read(binary) - parser.fetch(header.message_type).read(binary) + header = OpenFlow::Header.read(binary) + self.version = header.version + Parser.find_by_type!(header.type).read(binary) end - # rubocop:enable MethodLength - def self.set_message_class_name(klass_name, version) - open_flow_module = Pio.const_get(version) - return unless open_flow_module.const_defined?(klass_name) - Pio.__send__ :remove_const, klass_name if Pio.const_defined?(klass_name) - Pio.const_set(klass_name, open_flow_module.const_get(klass_name)) - rescue NameError - raise "#{version} is not supported yet." + def self.find_all_class_by_version(version) + all_class = [Message, Instruction, Action, FlowMatch]. + inject([]) do |result, each| + result + each.descendants + end + all_class.select do |each| + each.parents.include?(Class.const_get("Pio::#{version}")) + end end - private_class_method :set_message_class_name + private_class_method :find_all_class_by_version - # The default OpenFlow version is 1.0 - switch_version 'OpenFlow10' + # Pio::OpenFlow10::Hello -> Pio::Hello + def self.alias_open_flow_class(klass) + version = klass.name.split('::').second + class_name = klass.name.split('::').third + if Pio.const_defined?(class_name) + Pio.module_eval { remove_const class_name } + end + Pio.const_set(class_name, "Pio::#{version}::#{class_name}".constantize) + end + private_class_method :alias_open_flow_class end end + +# The default OpenFlow version is 1.0 +Pio::OpenFlow.version = :OpenFlow10 diff --git a/lib/pio/open_flow/action.rb b/lib/pio/open_flow/action.rb index 1b8db9a1..4f4f8d76 100644 --- a/lib/pio/open_flow/action.rb +++ b/lib/pio/open_flow/action.rb @@ -1,12 +1,11 @@ +require 'active_support/descendants_tracker' require 'bindata' module Pio module OpenFlow # OpenFlow actions. class Action - def self.inherited(child_klass) - child_klass.const_set :Format, Class.new(BinData::Record) - end + extend ActiveSupport::DescendantsTracker def self.action_header(options) module_eval do @@ -24,7 +23,12 @@ def self.read(raw_data) end def self.method_missing(method, *args, &block) - const_get(:Format).__send__ method, *args, &block + begin + const_get(:Format).__send__ method, *args, &block + rescue NameError + const_set :Format, Class.new(BinData::Record) + retry + end return if method == :endian || method == :virtual define_method(args.first) { @format.__send__ args.first } end diff --git a/lib/pio/open_flow/buffer_id.rb b/lib/pio/open_flow/buffer_id.rb new file mode 100644 index 00000000..d74a7083 --- /dev/null +++ b/lib/pio/open_flow/buffer_id.rb @@ -0,0 +1,19 @@ +module Pio + module OpenFlow + # Buffered packet to apply to, or :no_buffer. + class BufferId < BinData::Primitive + NO_BUFFER = 0xffffffff + + endian :big + uint32 :buffer_id, initial_value: NO_BUFFER + + def get + buffer_id == NO_BUFFER ? :no_buffer : buffer_id + end + + def set(value) + self.buffer_id = (value == :no_buffer ? NO_BUFFER : value) + end + end + end +end diff --git a/lib/pio/open_flow/datapath_id.rb b/lib/pio/open_flow/datapath_id.rb index a9576ee2..b18e0949 100644 --- a/lib/pio/open_flow/datapath_id.rb +++ b/lib/pio/open_flow/datapath_id.rb @@ -12,14 +12,14 @@ class DatapathId < BinData::Primitive def set(value) unless value.unsigned_64bit? - fail(ArgumentError, - 'Datapath ID should be an unsigned 64-bit integer.') + raise(ArgumentError, + 'Datapath ID should be an unsigned 64-bit integer.') end self.datapath_id = value end def get - datapath_id + datapath_id.to_i end end end diff --git a/lib/pio/open_flow/error.rb b/lib/pio/open_flow/error.rb deleted file mode 100644 index d2937601..00000000 --- a/lib/pio/open_flow/error.rb +++ /dev/null @@ -1,19 +0,0 @@ -module Pio - module OpenFlow - # Error message parser - class Error - def self.read(binary) - version = OpenFlowHeaderParser.read(binary).ofp_version - error_parser = case version - when 1 - Pio::OpenFlow10::Error - when 4 - Pio::OpenFlow13::Error - else - fail "Unsupported OpenFlow version: #{version}" - end - error_parser.read binary - end - end - end -end diff --git a/lib/pio/open_flow/error_message.rb b/lib/pio/open_flow/error_message.rb new file mode 100644 index 00000000..f350c43c --- /dev/null +++ b/lib/pio/open_flow/error_message.rb @@ -0,0 +1,32 @@ +require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/core_ext/string/inflections' + +module Pio + module OpenFlow + # Error message parser + module ErrorMessage + mattr_reader(:type) { 1 } + + # rubocop:disable AbcSize + def read(binary) + body = OpenFlow::Header.read(binary).body + error = const_get(:BodyParser).read(body) + klass = error_classes.find do |each| + each.name.split('::').last.underscore == error.error_type.to_s + end + unless klass + raise 'Unknown error message '\ + "(type=#{error.error_type}, code=#{error.error_code})" + end + klass.read binary + end + # rubocop:enable AbcSize + + def error_classes + OpenFlow::Message.descendants.select do |each| + each.parents.include? parent.const_get(:Error) + end + end + end + end +end diff --git a/lib/pio/open_flow/flags.rb b/lib/pio/open_flow/flags.rb index cc797e5f..cd756dfd 100644 --- a/lib/pio/open_flow/flags.rb +++ b/lib/pio/open_flow/flags.rb @@ -3,17 +3,21 @@ module OpenFlow # bitmap functions. # This class smells of :reek:DataClump module Flags + def define_flags_32bit(name, flags) + _define_flags name, 32, flags + end + def flags_32bit(name, flags) - _def_flags name, 32, flags + _flags name, 32, flags end def flags_16bit(name, flags) - _def_flags name, 16, flags + _flags name, 16, flags end # rubocop:disable MethodLength # This method smells of :reek:TooManyStatements - def _def_flags(name, size, flags) + def _define_flags(name, size, flags) flag_value = case flags when Array shift = 0 @@ -29,7 +33,7 @@ def _def_flags(name, size, flags) klass_name = name.to_s.split('_').map(&:capitalize).join flags_hash = flag_value.inspect - code = %{ + module_eval <<-end_eval, __FILE__, __LINE__ class #{klass_name} < BinData::Primitive endian :big @@ -53,8 +57,13 @@ def set(v) v.map { |each| list[each] }.inject(:|) end end - } - module_eval code + end_eval + end + # rubocop:enable MethodLength + + def _flags(name, size, flags_) + _define_flags name, size, flags_ + module_eval "#{name} :#{name}", __FILE__, __LINE__ end end end diff --git a/lib/pio/open_flow/flow_match.rb b/lib/pio/open_flow/flow_match.rb new file mode 100644 index 00000000..3c825c16 --- /dev/null +++ b/lib/pio/open_flow/flow_match.rb @@ -0,0 +1,10 @@ +require 'active_support/descendants_tracker' + +module Pio + module OpenFlow + # Flow match + class FlowMatch + extend ActiveSupport::DescendantsTracker + end + end +end diff --git a/lib/pio/open_flow/header.rb b/lib/pio/open_flow/header.rb new file mode 100644 index 00000000..a5cfd440 --- /dev/null +++ b/lib/pio/open_flow/header.rb @@ -0,0 +1,26 @@ +require 'bindata' +require 'pio/monkey_patch/uint' +require 'pio/open_flow/transaction_id' +require 'pio/open_flow/version' + +module Pio + module OpenFlow + # OpenFlow message header parser + class Header < BinData::Record + endian :big + + version :version + uint8 :type + uint16 :message_length + transaction_id :transaction_id + rest :body + + def to_bytes + [version, + type, + message_length, + transaction_id].map(&:to_bytes).join(', ') + end + end + end +end diff --git a/lib/pio/open_flow/hello_failed_code.rb b/lib/pio/open_flow/hello_failed_code.rb index 76f30c75..25faa437 100644 --- a/lib/pio/open_flow/hello_failed_code.rb +++ b/lib/pio/open_flow/hello_failed_code.rb @@ -4,7 +4,7 @@ module Pio module OpenFlow # enum ofp_hello_failed_code class HelloFailedCode < BinData::Primitive - ERROR_CODES = { incompatible: 0, permissions_error: 1 } + ERROR_CODES = { incompatible: 0, permissions_error: 1 }.freeze endian :big uint16 :error_code diff --git a/lib/pio/open_flow/instruction.rb b/lib/pio/open_flow/instruction.rb new file mode 100644 index 00000000..1901fb90 --- /dev/null +++ b/lib/pio/open_flow/instruction.rb @@ -0,0 +1,10 @@ +require 'active_support/descendants_tracker' + +module Pio + module OpenFlow + # Flow instruction + class Instruction + extend ActiveSupport::DescendantsTracker + end + end +end diff --git a/lib/pio/open_flow/message.rb b/lib/pio/open_flow/message.rb index f0a09212..49eebbca 100644 --- a/lib/pio/open_flow/message.rb +++ b/lib/pio/open_flow/message.rb @@ -1,15 +1,19 @@ +require 'active_support/core_ext/module/attribute_accessors' +require 'active_support/descendants_tracker' require 'bindata' +require 'pio/open_flow/flags' +require 'pio/open_flow/header' require 'pio/parse_error' -require 'pio/open_flow/open_flow_header' +require 'pio/ruby_dumper' module Pio module OpenFlow # OpenFlow messages. class Message - def self.inherited(child_klass) - child_klass.const_set :Format, Class.new(BinData::Record) - child_klass.class_variable_set(:@@valid_options, []) - end + attr_reader :format + + extend ActiveSupport::DescendantsTracker + extend OpenFlow::Flags def self.read(raw_data) allocate.tap do |message| @@ -21,31 +25,62 @@ def self.read(raw_data) raise Pio::ParseError, "Invalid #{message_name} message." end + # rubocop:disable MethodLength + # rubocop:disable AbcSize def self.method_missing(method, *args, &block) - const_get(:Format).__send__ method, *args, &block + begin + const_get(:Format).__send__ method, *args, &block + rescue NameError + klass = Class.new(BinData::Record) + const_set :Format, klass + klass.class_eval do + include RubyDumper + define_method(:header_length) { 8 } + define_method(:length) { _length } + end + class_variable_set(:@@valid_options, []) + retry + end return if method == :endian || method == :virtual + define_method(args.first) do - @format.__send__ args.first + snapshot = @format.snapshot.__send__(args.first) + if snapshot.class == BinData::Struct::Snapshot + @format.__send__(args.first) + else + snapshot + end end class_variable_set(:@@valid_options, class_variable_get(:@@valid_options) + [args.first]) end + # rubocop:enable MethodLength + # rubocop:enable AbcSize # rubocop:disable AbcSize # rubocop:disable MethodLength def self.open_flow_header(opts) module_eval do + cattr_reader(:type) { opts.fetch(:type) } + endian :big - uint8 :ofp_version, value: opts.fetch(:version) - virtual assert: -> { ofp_version == opts.fetch(:version) } - uint8 :message_type, value: opts.fetch(:message_type) - virtual assert: -> { message_type == opts.fetch(:message_type) } - uint16 :message_length, - initial_value: opts[:message_length] || -> { 8 + body.length } + uint8 :version, value: opts.fetch(:version) + uint8 :type, value: opts.fetch(:type) + uint16(:_length, + initial_value: opts[:length] || lambda do + begin + 8 + body.length + rescue + 8 + end + end) transaction_id :transaction_id, initial_value: 0 + virtual assert: -> { version == opts.fetch(:version) } + virtual assert: -> { type == opts.fetch(:type) } + alias_method :xid, :transaction_id end end @@ -71,7 +106,7 @@ def validate_user_options(user_options) unknown_options = user_options.keys - self.class.class_variable_get(:@@valid_options) return if unknown_options.empty? - fail "Unknown option: #{unknown_options.first}" + raise "Unknown option: #{unknown_options.first}" end def parse_options(user_options) diff --git a/lib/pio/open_flow/nicira_resubmit.rb b/lib/pio/open_flow/nicira_resubmit.rb index 61925cdc..216fe20f 100644 --- a/lib/pio/open_flow/nicira_resubmit.rb +++ b/lib/pio/open_flow/nicira_resubmit.rb @@ -1,18 +1,20 @@ require 'pio/open_flow/nicira_action' -require 'pio/open_flow10/port16' module Pio - # NXAST_RESUBMIT action - class NiciraResubmit < OpenFlow::NiciraAction - nicira_action_header action_type: 0xffff, - action_length: 16, - subtype: 1 - port16 :in_port - string :padding, length: 4 - hide :padding + module OpenFlow + # NXAST_RESUBMIT action + class NiciraResubmit < OpenFlow::NiciraAction + nicira_action_header action_type: 0xffff, + action_length: 16, + subtype: 1 + uint16 :in_port + string :padding, length: 4 + hide :padding - def initialize(port_number) - super(in_port: port_number) + def initialize(port_number) + super(in_port: port_number) + end end end + NiciraResubmit = OpenFlow::NiciraResubmit end diff --git a/lib/pio/open_flow/nicira_resubmit_table.rb b/lib/pio/open_flow/nicira_resubmit_table.rb index 5ea9ab97..6d30d221 100644 --- a/lib/pio/open_flow/nicira_resubmit_table.rb +++ b/lib/pio/open_flow/nicira_resubmit_table.rb @@ -1,15 +1,22 @@ require 'pio/open_flow/nicira_action' -require 'pio/open_flow10/port16' module Pio - # NXAST_RESUBMIT_TABLE action - class NiciraResubmitTable < OpenFlow::NiciraAction - nicira_action_header action_type: 0xffff, - action_length: 16, - subtype: 14 - port16 :in_port - uint8 :table, initial_value: 0xff - string :padding, length: 3 - hide :padding + module OpenFlow + # NXAST_RESUBMIT_TABLE action + class NiciraResubmitTable < OpenFlow::NiciraAction + nicira_action_header action_type: 0xffff, + action_length: 16, + subtype: 14 + uint16 :in_port + uint8 :table, initial_value: 0xff + string :padding, length: 3 + hide :padding + + def initialize(options) + raise ':in_port option is a mandatory' unless options.key?(:in_port) + super options + end + end end + NiciraResubmitTable = OpenFlow::NiciraResubmitTable end diff --git a/lib/pio/open_flow/open_flow_header.rb b/lib/pio/open_flow/open_flow_header.rb deleted file mode 100644 index bc894866..00000000 --- a/lib/pio/open_flow/open_flow_header.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'bindata' -require 'pio/open_flow/transaction_id' - -module Pio - # OpenFlow message header parser - class OpenFlowHeaderParser < BinData::Record - endian :big - - uint8 :ofp_version - uint8 :message_type - uint16 :message_length - transaction_id :transaction_id - rest :body - end - - # OpenFlow message header. - class OfpHeader < BinData::Record - endian :big - - uint8 :ofp_version, value: :version_value - virtual assert: -> { ofp_version == version_value } - uint8 :message_type, value: :message_type_value - virtual assert: -> { message_type == message_type_value } - uint16 :message_length, initial_value: -> { length + body_length } - transaction_id :transaction_id, initial_value: 0 - - def length - 8 - end - end -end diff --git a/lib/pio/open_flow/parser.rb b/lib/pio/open_flow/parser.rb new file mode 100644 index 00000000..73594131 --- /dev/null +++ b/lib/pio/open_flow/parser.rb @@ -0,0 +1,19 @@ +require 'pio/open_flow10' +require 'pio/open_flow13' + +module Pio + module OpenFlow + # Collection class of OpenFlow message parser class + class Parser + def self.find_by_type!(type) + message_class = [Hello, Error, Echo::Request, Echo::Reply, + Features::Request, Features::Reply, PacketIn, + PacketOut, FlowMod, PortStatus, Stats::Request, + Stats::Reply, Barrier::Request, Barrier::Reply] + message_class.each_with_object({}) do |each, hash| + hash[each.type] = each + end.fetch(type) + end + end + end +end diff --git a/lib/pio/open_flow/port.rb b/lib/pio/open_flow/port.rb index 9b806a8b..34be270c 100644 --- a/lib/pio/open_flow/port.rb +++ b/lib/pio/open_flow/port.rb @@ -57,9 +57,9 @@ def set(port) self.port = reserved_port_number(port) else port_num = port.to_i - fail ArgumentError, 'The port should be > 0' if port_num < 1 + raise ArgumentError, 'The port should be > 0' if port_num < 1 if port_num >= max - fail ArgumentError, "The port should be < #{max.to_hex}" + raise ArgumentError, "The port should be < #{max.to_hex}" end self.port = port_num end diff --git a/lib/pio/open_flow/transaction_id.rb b/lib/pio/open_flow/transaction_id.rb index bbbfdf39..d7e93672 100644 --- a/lib/pio/open_flow/transaction_id.rb +++ b/lib/pio/open_flow/transaction_id.rb @@ -11,8 +11,8 @@ class TransactionId < BinData::Primitive def set(value) unless value.unsigned_32bit? - fail(ArgumentError, - 'Transaction ID should be an unsigned 32-bit integer.') + raise(ArgumentError, + 'Transaction ID should be an unsigned 32-bit integer.') end self.xid = value end diff --git a/lib/pio/open_flow/version.rb b/lib/pio/open_flow/version.rb new file mode 100644 index 00000000..eff1659d --- /dev/null +++ b/lib/pio/open_flow/version.rb @@ -0,0 +1,22 @@ +module Pio + module OpenFlow + # OpenFlow version + class Version < BinData::Primitive + VERSIONS = { 1 => :OpenFlow10, 4 => :OpenFlow13 }.freeze + + uint8 :version + + def get + VERSIONS.fetch(version) + end + + def set(value) + self.version = VERSIONS.invert.fetch(value) + end + + def to_bytes + version.to_hex + end + end + end +end diff --git a/lib/pio/open_flow10/actions.rb b/lib/pio/open_flow10/actions.rb index 950fa12d..a924ff03 100644 --- a/lib/pio/open_flow10/actions.rb +++ b/lib/pio/open_flow10/actions.rb @@ -15,22 +15,22 @@ module Pio module OpenFlow # Actions list. - class Actions < BinData::Primitive + class Actions10 < BinData::Primitive ACTION_CLASS = { - 0 => Pio::OpenFlow10::SendOutPort, - 1 => Pio::OpenFlow10::SetVlanVid, - 2 => Pio::OpenFlow10::SetVlanPriority, - 3 => Pio::OpenFlow10::StripVlanHeader, - 4 => Pio::OpenFlow10::SetSourceMacAddress, - 5 => Pio::OpenFlow10::SetDestinationMacAddress, - 6 => Pio::OpenFlow10::SetSourceIpAddress, - 7 => Pio::OpenFlow10::SetDestinationIpAddress, - 8 => Pio::OpenFlow10::SetTos, - 9 => Pio::OpenFlow10::SetTransportSourcePort, - 10 => Pio::OpenFlow10::SetTransportDestinationPort, - 11 => Pio::OpenFlow10::Enqueue, - 0xffff => Pio::VendorAction - } + 0 => OpenFlow10::SendOutPort, + 1 => OpenFlow10::SetVlanVid, + 2 => OpenFlow10::SetVlanPriority, + 3 => OpenFlow10::StripVlanHeader, + 4 => OpenFlow10::SetSourceMacAddress, + 5 => OpenFlow10::SetDestinationMacAddress, + 6 => OpenFlow10::SetSourceIpAddress, + 7 => OpenFlow10::SetDestinationIpAddress, + 8 => OpenFlow10::SetTos, + 9 => OpenFlow10::SetTransportSourcePort, + 10 => OpenFlow10::SetTransportDestinationPort, + 11 => OpenFlow10::Enqueue, + 0xffff => OpenFlow10::VendorAction + }.freeze mandatory_parameter :length @@ -47,7 +47,7 @@ def set(actions) def get actions = [] tmp = binary - while tmp.length > 0 + until tmp.empty? type = BinData::Uint16be.read(tmp) begin action = ACTION_CLASS.fetch(type).read(tmp) diff --git a/lib/pio/open_flow10/aggregate_stats/reply.rb b/lib/pio/open_flow10/aggregate_stats/reply.rb index 3f82d971..7e2e6e32 100644 --- a/lib/pio/open_flow10/aggregate_stats/reply.rb +++ b/lib/pio/open_flow10/aggregate_stats/reply.rb @@ -1,3 +1,4 @@ +require 'pio/open_flow/message' require 'pio/open_flow10/stats_type' module Pio @@ -6,7 +7,7 @@ module OpenFlow10 module AggregateStats # Aggregate Stats Reply message class Reply < OpenFlow::Message - open_flow_header version: 1, message_type: 17, message_length: 32 + open_flow_header version: 1, type: 17, length: 32 stats_type :stats_type, value: -> { :aggregate } uint16 :flags uint64 :packet_count diff --git a/lib/pio/open_flow10/aggregate_stats/request.rb b/lib/pio/open_flow10/aggregate_stats/request.rb index 0f411059..de5b4f76 100644 --- a/lib/pio/open_flow10/aggregate_stats/request.rb +++ b/lib/pio/open_flow10/aggregate_stats/request.rb @@ -9,9 +9,7 @@ module OpenFlow10 module AggregateStats # OpenFlow 1.0 Aggregate Stats Request message class Request < OpenFlow::Message - open_flow_header version: 1, - message_type: 16, - message_length: 56 + open_flow_header version: 1, type: 16, length: 56 stats_type :stats_type, value: -> { :aggregate } uint16 :flags match10 :match diff --git a/lib/pio/open_flow10/barrier/reply.rb b/lib/pio/open_flow10/barrier/reply.rb index 9a7e49ce..de9dd20d 100644 --- a/lib/pio/open_flow10/barrier/reply.rb +++ b/lib/pio/open_flow10/barrier/reply.rb @@ -6,7 +6,7 @@ module OpenFlow10 module Barrier # OpenFlow 1.0 Barrier Request message class Reply < OpenFlow::Message - open_flow_header version: 1, message_type: 19 + open_flow_header version: 1, type: 19 string :body, value: '' end end diff --git a/lib/pio/open_flow10/barrier/request.rb b/lib/pio/open_flow10/barrier/request.rb index 6c10ee8c..0ff9138e 100644 --- a/lib/pio/open_flow10/barrier/request.rb +++ b/lib/pio/open_flow10/barrier/request.rb @@ -6,7 +6,7 @@ module OpenFlow10 module Barrier # OpenFlow 1.0 Barrier Request message class Request < OpenFlow::Message - open_flow_header version: 1, message_type: 18 + open_flow_header version: 1, type: 18 string :body, value: '' end end diff --git a/lib/pio/open_flow10/description_stats/reply.rb b/lib/pio/open_flow10/description_stats/reply.rb index 8a09398f..1c387d66 100644 --- a/lib/pio/open_flow10/description_stats/reply.rb +++ b/lib/pio/open_flow10/description_stats/reply.rb @@ -6,7 +6,7 @@ module OpenFlow10 module DescriptionStats # OpenFlow 1.0 Description Stats Reply message class Reply < OpenFlow::Message - open_flow_header version: 1, message_type: 17, message_length: 1068 + open_flow_header version: 1, type: 17, length: 1068 stats_type :stats_type, value: -> { :description } uint16 :flags diff --git a/lib/pio/open_flow10/description_stats/request.rb b/lib/pio/open_flow10/description_stats/request.rb index ec921c09..f60b724f 100644 --- a/lib/pio/open_flow10/description_stats/request.rb +++ b/lib/pio/open_flow10/description_stats/request.rb @@ -7,9 +7,7 @@ module OpenFlow10 module DescriptionStats # OpenFlow 1.0 Description Stats Request message class Request < OpenFlow::Message - open_flow_header version: 1, - message_type: 16, - message_length: 12 + open_flow_header version: 1, type: 16, length: 12 stats_type :stats_type, value: -> { :description } uint16 :flags string :body, value: '' diff --git a/lib/pio/open_flow10/echo/reply.rb b/lib/pio/open_flow10/echo/reply.rb index 1f8957e0..159c978a 100644 --- a/lib/pio/open_flow10/echo/reply.rb +++ b/lib/pio/open_flow10/echo/reply.rb @@ -6,10 +6,10 @@ module OpenFlow10 module Echo # OpenFlow 1.0 Echo Reply message. class Reply < OpenFlow::Message - open_flow_header version: 1, message_type: 3 - string :body, read_length: -> { message_length - 8 } + open_flow_header version: 1, type: 3 + string :body, read_length: -> { length - header_length } - alias_method :user_data, :body + alias user_data body end end end diff --git a/lib/pio/open_flow10/echo/request.rb b/lib/pio/open_flow10/echo/request.rb index 40b245ed..5fb38957 100644 --- a/lib/pio/open_flow10/echo/request.rb +++ b/lib/pio/open_flow10/echo/request.rb @@ -6,10 +6,10 @@ module OpenFlow10 module Echo # OpenFlow 1.0 Echo Request message. class Request < OpenFlow::Message - open_flow_header version: 1, message_type: 2 - string :body, read_length: -> { message_length - 8 } + open_flow_header version: 1, type: 2 + string :body, read_length: -> { length - header_length } - alias_method :user_data, :body + alias user_data body end end end diff --git a/lib/pio/open_flow10/enqueue.rb b/lib/pio/open_flow10/enqueue.rb index f17684b9..4ed3223b 100644 --- a/lib/pio/open_flow10/enqueue.rb +++ b/lib/pio/open_flow10/enqueue.rb @@ -24,8 +24,8 @@ def initialize(user_options) def validate_port(user_options) port = user_options.fetch(:port) if port.is_a?(Symbol) && port != :in_port - fail(ArgumentError, - ':port must be a valid physical port or :in_port') + raise(ArgumentError, + ':port must be a valid physical port or :in_port') end rescue KeyError raise ArgumentError, ':port is a mandatory option' @@ -33,7 +33,7 @@ def validate_port(user_options) def validate_queue_id(user_options) unless user_options.fetch(:queue_id).unsigned_32bit? - fail ArgumentError, ':queue_id must be an unsigned 32-bit integer' + raise ArgumentError, ':queue_id must be an unsigned 32-bit integer' end rescue KeyError raise ArgumentError, ':queue_id is a mandatory option' diff --git a/lib/pio/open_flow10/error.rb b/lib/pio/open_flow10/error.rb index dc6d9dfb..f5ae21c4 100644 --- a/lib/pio/open_flow10/error.rb +++ b/lib/pio/open_flow10/error.rb @@ -1,28 +1,18 @@ +require 'pio/open_flow/error_message' require 'pio/open_flow10/error/error_type10' module Pio module OpenFlow10 # Error message parser module Error + extend OpenFlow::ErrorMessage + # Error message body parser. class BodyParser < BinData::Record endian :big error_type10 :error_type uint16 :error_code end - - def self.read(binary) - body = OpenFlowHeaderParser.read(binary).body - klass = case BodyParser.read(body).snapshot.error_type - when :hello_failed - HelloFailed - when :bad_request - BadRequest - else - fail 'Unknown error message.' - end - klass.read binary - end end end end diff --git a/lib/pio/open_flow10/error/bad_request.rb b/lib/pio/open_flow10/error/bad_request.rb index 4757182d..8260b6f4 100644 --- a/lib/pio/open_flow10/error/bad_request.rb +++ b/lib/pio/open_flow10/error/bad_request.rb @@ -1,4 +1,6 @@ +require 'pio/open_flow/error_message' require 'pio/open_flow/message' +require 'pio/open_flow10/error/bad_request/bad_request_code' require 'pio/open_flow10/error/error_type10' module Pio @@ -6,38 +8,12 @@ module OpenFlow10 module Error # Bad request error. class BadRequest < OpenFlow::Message - # enum ofp_bad_request_code - class BadRequestCode < BinData::Primitive - ERROR_CODES = { - bad_version: 0, - bad_type: 1, - bad_stats: 2, - bad_vendor: 3, - bad_subtype: 4, - permissions_error: 5, - bad_length: 6, - buffer_empty: 7, - buffer_unknown: 8 - } - - endian :big - uint16 :error_code - - def get - ERROR_CODES.invert.fetch(error_code) - end - - def set(value) - self.error_code = ERROR_CODES.fetch(value) - end - end - open_flow_header version: 1, - message_type: 1, - message_length: -> { 12 + raw_data.length } + type: OpenFlow::ErrorMessage.type, + length: -> { header_length + 4 + raw_data.length } error_type10 :error_type, value: -> { :bad_request } bad_request_code :error_code - rest :raw_data + string :raw_data, read_length: -> { length - header_length - 4 } end end end diff --git a/lib/pio/open_flow10/error/bad_request/bad_request_code.rb b/lib/pio/open_flow10/error/bad_request/bad_request_code.rb new file mode 100644 index 00000000..ca4fa01c --- /dev/null +++ b/lib/pio/open_flow10/error/bad_request/bad_request_code.rb @@ -0,0 +1,35 @@ +require 'bindata' + +module Pio + module OpenFlow10 + module Error + class BadRequest < OpenFlow::Message + # enum ofp_bad_request_code + class BadRequestCode < BinData::Primitive + ERROR_CODES = { + bad_version: 0, + bad_type: 1, + bad_stats: 2, + bad_vendor: 3, + bad_subtype: 4, + permissions_error: 5, + bad_length: 6, + buffer_empty: 7, + buffer_unknown: 8 + }.freeze + + endian :big + uint16 :error_code + + def get + ERROR_CODES.invert.fetch(error_code) + end + + def set(value) + self.error_code = ERROR_CODES.fetch(value) + end + end + end + end + end +end diff --git a/lib/pio/open_flow10/error/error_type10.rb b/lib/pio/open_flow10/error/error_type10.rb index b659e11d..fdffd6d8 100644 --- a/lib/pio/open_flow10/error/error_type10.rb +++ b/lib/pio/open_flow10/error/error_type10.rb @@ -10,7 +10,7 @@ class ErrorType10 < BinData::Primitive flow_mod_failed: 3, port_mod_failed: 4, queue_operation_failed: 5 - } + }.freeze endian :big uint16 :error_type diff --git a/lib/pio/open_flow10/error/hello_failed.rb b/lib/pio/open_flow10/error/hello_failed.rb index b7f198e7..9778fa71 100644 --- a/lib/pio/open_flow10/error/hello_failed.rb +++ b/lib/pio/open_flow10/error/hello_failed.rb @@ -1,3 +1,4 @@ +require 'pio/open_flow/error_message' require 'pio/open_flow/hello_failed_code' require 'pio/open_flow/message' require 'pio/open_flow10/error/error_type10' @@ -8,11 +9,12 @@ module Error # Hello failed error. class HelloFailed < OpenFlow::Message open_flow_header version: 1, - message_type: 1, - message_length: -> { 12 + description.length } - error_type10 :error_type + type: OpenFlow::ErrorMessage.type, + length: -> { header_length + 4 + description.length } + + error_type10 :error_type, value: -> { :hello_failed } hello_failed_code :error_code - rest :description + string :description, read_length: -> { length - header_length - 4 } end end end diff --git a/lib/pio/open_flow10/exact_match.rb b/lib/pio/open_flow10/exact_match.rb index fd58249a..3112db24 100644 --- a/lib/pio/open_flow10/exact_match.rb +++ b/lib/pio/open_flow10/exact_match.rb @@ -1,17 +1,19 @@ require 'pio/open_flow10/match' module Pio - # OpenFlow 1.0 exact match - class ExactMatch - def initialize(packet_in) - @match = packet_in.data.to_exact_match(packet_in.in_port) - rescue NoMethodError - raise NotImplementedError, - "#{packet_in.data.class} is not yet supported by ExactMatch." - end + module OpenFlow10 + # OpenFlow 1.0 exact match + class ExactMatch < OpenFlow::FlowMatch + def initialize(packet_in) + @match = packet_in.data.to_exact_match(packet_in.in_port) + rescue NoMethodError + raise NotImplementedError, + "#{packet_in.data.class} is not yet supported by ExactMatch." + end - def method_missing(method, *args, &block) - @match.__send__ method, *args, &block + def method_missing(method, *args, &block) + @match.__send__ method, *args, &block + end end end end diff --git a/lib/pio/open_flow10/features/reply.rb b/lib/pio/open_flow10/features/reply.rb index 99428e6c..504958b3 100644 --- a/lib/pio/open_flow10/features/reply.rb +++ b/lib/pio/open_flow10/features/reply.rb @@ -1,4 +1,5 @@ -require 'pio/open_flow' +require 'pio/open_flow/datapath_id' +require 'pio/open_flow/message' require 'pio/open_flow10/phy_port16' require 'pio/open_flow10/port16' @@ -8,9 +9,18 @@ module OpenFlow10 class Features # OpenFlow 1.0 Features Reply message. class Reply < OpenFlow::Message - extend OpenFlow::Flags + open_flow_header(version: 1, + type: 6, + length: lambda do + header_length + 24 + PhyPort16.length * ports.length + end) - # enum ofp_capabilities + datapath_id :datapath_id + alias dpid datapath_id + uint32 :n_buffers + uint8 :n_tables + string :padding, length: 3 + hide :padding flags_32bit :capabilities, [:flow_stats, :table_stats, @@ -20,9 +30,7 @@ class Reply < OpenFlow::Message :ip_reasm, :queue_stats, :arp_match_ip] - - # enum ofp_action_type - flags_32bit :actions_flag, + flags_32bit :actions, [:output, :set_vlan_vid, :set_vlan_pcp, @@ -35,37 +43,14 @@ class Reply < OpenFlow::Message :set_transport_source_port, :set_transport_destination_port, :enqueue] - - open_flow_header version: 1, - message_type: 6, - message_length: -> { 32 + ports.to_binary_s.length } - - datapath_id :datapath_id - uint32 :n_buffers - uint8 :n_tables - uint24 :padding - hide :padding - capabilities :capabilities - actions_flag :actions array :ports, type: :phy_port16, read_until: :eof - def datapath_id - @format.datapath_id.to_i - end - alias_method :dpid, :datapath_id - def ports - snapshot.ports.map do |each| - each.instance_variable_set :@datapath_id, datapath_id + super.map do |each| + each.datapath_id = datapath_id each end end - - def physical_ports - ports.select do |each| - each.port_no <= OpenFlow10::Port16::MAX - end - end end end end diff --git a/lib/pio/open_flow10/features/request.rb b/lib/pio/open_flow10/features/request.rb index 75d94b9b..41a56bc7 100644 --- a/lib/pio/open_flow10/features/request.rb +++ b/lib/pio/open_flow10/features/request.rb @@ -6,10 +6,7 @@ module OpenFlow10 class Features # Features Request message. class Request < OpenFlow::Message - open_flow_header version: 1, message_type: 5 - string :body, length: 0 - - alias_method :user_data, :body + open_flow_header version: 1, type: 5 end end end diff --git a/lib/pio/open_flow10/flow_mod.rb b/lib/pio/open_flow10/flow_mod.rb index 2e318bc2..ece0f94a 100644 --- a/lib/pio/open_flow10/flow_mod.rb +++ b/lib/pio/open_flow10/flow_mod.rb @@ -1,43 +1,15 @@ require 'pio/open_flow' require 'pio/open_flow10/actions' +require 'pio/open_flow10/flow_mod/command' require 'pio/open_flow10/match10' module Pio module OpenFlow10 # OpenFlow 1.0 Flow Mod message. class FlowMod < OpenFlow::Message - # enum ofp_flow_mod_command - class Command < BinData::Primitive - COMMANDS = { - add: 0, - modify: 1, - modify_strict: 2, - delete: 3, - delete_strict: 4 - } + open_flow_header version: 1, type: 14, + length: -> { 72 + actions.binary.length } - endian :big - uint16 :command - - def get - COMMANDS.invert.fetch(command) - end - - def set(value) - self.command = COMMANDS.fetch(value) - end - end - - extend OpenFlow::Flags - - flags_16bit :flags, - [:send_flow_rem, - :check_overwrap, - :emerg] - - open_flow_header version: 1, - message_type: 14, - message_length: -> { 72 + actions.binary.length } match10 :match uint64 :cookie command :command @@ -46,8 +18,11 @@ def set(value) uint16 :priority uint32 :buffer_id uint16 :out_port - flags :flags - actions :actions, length: -> { message_length - 72 } + flags_16bit :flags, + [:send_flow_rem, + :check_overwrap, + :emerg] + actions10 :actions, length: -> { length - 72 } end end end diff --git a/lib/pio/open_flow10/flow_mod/command.rb b/lib/pio/open_flow10/flow_mod/command.rb new file mode 100644 index 00000000..8d4a1621 --- /dev/null +++ b/lib/pio/open_flow10/flow_mod/command.rb @@ -0,0 +1,28 @@ +module Pio + module OpenFlow10 + # OpenFlow 1.0 Flow Mod message. + class FlowMod < OpenFlow::Message + # enum ofp_flow_mod_command + class Command < BinData::Primitive + COMMANDS = { + add: 0, + modify: 1, + modify_strict: 2, + delete: 3, + delete_strict: 4 + }.freeze + + endian :big + uint16 :command + + def get + COMMANDS.invert.fetch(command) + end + + def set(value) + self.command = COMMANDS.fetch(value) + end + end + end + end +end diff --git a/lib/pio/open_flow10/flow_removed.rb b/lib/pio/open_flow10/flow_removed.rb index 5273db69..c7de9c6c 100644 --- a/lib/pio/open_flow10/flow_removed.rb +++ b/lib/pio/open_flow10/flow_removed.rb @@ -1,27 +1,12 @@ require 'pio/open_flow/message' require 'pio/open_flow10/match10' +require 'pio/open_flow10/flow_removed/reason' module Pio module OpenFlow10 # Flow Removed message class FlowRemoved < OpenFlow::Message - # Why was this flow removed? - # (enum ofp_flow_removed_reason) - class Reason < BinData::Primitive - REASONS = { idle_timeout: 0, hard_timeout: 1, delete: 2 } - - uint8 :reason - - def get - REASONS.invert.fetch(reason) - end - - def set(value) - self.reason = REASONS.fetch(value) - end - end - - open_flow_header version: 1, message_type: 11, message_length: 88 + open_flow_header version: 1, type: 11, length: 88 match10 :match uint64 :cookie uint16 :priority diff --git a/lib/pio/open_flow10/flow_removed/reason.rb b/lib/pio/open_flow10/flow_removed/reason.rb new file mode 100644 index 00000000..cf9001df --- /dev/null +++ b/lib/pio/open_flow10/flow_removed/reason.rb @@ -0,0 +1,22 @@ +module Pio + module OpenFlow10 + # Flow Removed message + class FlowRemoved < OpenFlow::Message + # Why was this flow removed? + # (enum ofp_flow_removed_reason) + class Reason < BinData::Primitive + REASONS = { idle_timeout: 0, hard_timeout: 1, delete: 2 }.freeze + + uint8 :reason + + def get + REASONS.invert.fetch(reason) + end + + def set(value) + self.reason = REASONS.fetch(value) + end + end + end + end +end diff --git a/lib/pio/open_flow10/flow_stats/reply.rb b/lib/pio/open_flow10/flow_stats/reply.rb index 4c1750f2..a04fc57f 100644 --- a/lib/pio/open_flow10/flow_stats/reply.rb +++ b/lib/pio/open_flow10/flow_stats/reply.rb @@ -27,12 +27,11 @@ class FlowStatsEntry < BinData::Record uint64 :cookie uint64 :packet_count uint64 :byte_count - actions :actions, length: -> { entry_length - 88 } + actions10 :actions, length: -> { entry_length - 88 } end - open_flow_header version: 1, - message_type: 17, - message_length: -> { 12 + stats.to_binary_s.length } + open_flow_header version: 1, type: 17, + length: -> { 12 + stats.to_binary_s.length } stats_type :stats_type, value: -> { :flow } uint16 :flags diff --git a/lib/pio/open_flow10/flow_stats/request.rb b/lib/pio/open_flow10/flow_stats/request.rb index 35881438..68a2cda3 100644 --- a/lib/pio/open_flow10/flow_stats/request.rb +++ b/lib/pio/open_flow10/flow_stats/request.rb @@ -8,9 +8,7 @@ module OpenFlow10 module FlowStats # OpenFlow 1.0 Flow Stats Request message class Request < OpenFlow::Message - open_flow_header version: 1, - message_type: 16, - message_length: 56 + open_flow_header version: 1, type: 16, length: 56 stats_type :stats_type, value: -> { :flow } uint16 :flags diff --git a/lib/pio/open_flow10/hello.rb b/lib/pio/open_flow10/hello.rb index 9fb41094..86143465 100644 --- a/lib/pio/open_flow10/hello.rb +++ b/lib/pio/open_flow10/hello.rb @@ -4,10 +4,7 @@ module Pio module OpenFlow10 # Hello message class Hello < OpenFlow::Message - open_flow_header version: 1, message_type: 0 - string :body, length: 0 - - alias_method :user_data, :body + open_flow_header version: 1, type: 0 end end end diff --git a/lib/pio/open_flow10/match.rb b/lib/pio/open_flow10/match.rb index f6a1f312..642ae1b7 100644 --- a/lib/pio/open_flow10/match.rb +++ b/lib/pio/open_flow10/match.rb @@ -1,12 +1,13 @@ require 'English' require 'bindata' +require 'pio/open_flow/flow_match' require 'pio/type/ip_address' require 'pio/type/mac_address' module Pio module OpenFlow10 # Fields to match against flows - class Match + class Match < OpenFlow::FlowMatch # Flow wildcards class Wildcards < BinData::Primitive BITS = { @@ -34,8 +35,8 @@ class Wildcards < BinData::Primitive destination_ip_address_all: 1 << 19, vlan_priority: 1 << 20, tos: 1 << 21 - } - NW_FLAGS = [:source_ip_address, :destination_ip_address] + }.freeze + NW_FLAGS = [:source_ip_address, :destination_ip_address].freeze FLAGS = BITS.keys.select { |each| !(/^(source|destination)_ip/=~ each) } endian :big @@ -45,7 +46,7 @@ class Wildcards < BinData::Primitive # This method smells of :reek:FeatureEnvy def get BITS.each_with_object(Hash.new(0)) do |(key, bit), memo| - next if flags & bit == 0 + next if (flags & bit).zero? if /(source_ip_address|destination_ip_address)(\d)/=~ key memo[$LAST_MATCH_INFO[1].to_sym] |= 1 << $LAST_MATCH_INFO[2].to_i else @@ -56,13 +57,13 @@ def get def set(params) self.flags = params.inject(0) do |memo, (key, val)| - memo | case key - when :source_ip_address, :destination_ip_address - (params.fetch(key) & 31) << - (key == :source_ip_address ? 8 : 14) - else - val ? BITS.fetch(key) : 0 - end + memo | + case key + when :source_ip_address, :destination_ip_address + (params.fetch(key) & 31) << (key == :source_ip_address ? 8 : 14) + else + val ? BITS.fetch(key) : 0 + end end end @@ -143,7 +144,7 @@ def initialize(user_options = {}) memo["#{each}_all".to_sym] = true end end - @format = MatchFormat.new({ wildcards: flags }.merge user_options) + @format = MatchFormat.new({ wildcards: flags }.merge(user_options)) end # rubocop:enable MethodLength diff --git a/lib/pio/open_flow10/packet_in.rb b/lib/pio/open_flow10/packet_in.rb index 79b1317e..ea9ae08f 100644 --- a/lib/pio/open_flow10/packet_in.rb +++ b/lib/pio/open_flow10/packet_in.rb @@ -1,39 +1,21 @@ -require 'pio/ethernet_header' -require 'pio/ipv4_header' -require 'pio/open_flow' -require 'pio/parse_error' +require 'active_support/core_ext/object/try' +require 'pio/open_flow/message' +require 'pio/open_flow10/packet_in/reason' require 'pio/parser' module Pio module OpenFlow10 # OpenFlow 1.0 Packet-In message class PacketIn < OpenFlow::Message - # Why is this packet being sent to the controller? - # (enum ofp_packet_in_reason) - class Reason < BinData::Primitive - REASONS = { no_match: 0, action: 1 } - - uint8 :reason - - def get - REASONS.invert.fetch(reason) - end - - def set(value) - self.reason = REASONS.fetch(value) - end - end - open_flow_header version: 1, - message_type: 10, - message_length: -> { 18 + raw_data.length } + type: 10, + length: -> { header_length + 10 + raw_data.length } uint32 :buffer_id - uint16 :total_len, value: -> { raw_data.length } + uint16 :total_length, initial_value: -> { raw_data.length } uint16 :in_port reason :reason uint8 :padding - hide :padding - string :raw_data, read_length: :total_len + string :raw_data, read_length: -> { length - header_length - 10 } def data @data ||= Pio::Parser.read(raw_data) @@ -43,13 +25,35 @@ def lldp? data.is_a? Lldp end + def to_ruby + @format.to_ruby + end + + # rubocop:disable LineLength + def self.inspect + 'PacketIn(open_flow_version: uint8, message_type: uint8, message_length: uint16, transaction_id: uint32, buffer_id: uint32, total_length: uint16, in_port: uint16, reason: symbol, raw_data: string)' + end + # rubocop:enable LineLength + + # rubocop:disable LineLength + def inspect + data_inspection = if raw_data.empty? + %(raw_data: "") + else + %(data: #{data.inspect}) + end + %(#) + end + # rubocop:enable LineLength + def method_missing(method, *args) - data.__send__(method, *args).snapshot + bindata_value = data.__send__(method, *args) + bindata_value.try(:snapshot) || bindata_value end attr_accessor :datapath_id - alias_method :dpid, :datapath_id - alias_method :dpid=, :datapath_id= + alias dpid datapath_id + alias dpid= datapath_id= end end end diff --git a/lib/pio/open_flow10/packet_in/reason.rb b/lib/pio/open_flow10/packet_in/reason.rb new file mode 100644 index 00000000..226c4bb3 --- /dev/null +++ b/lib/pio/open_flow10/packet_in/reason.rb @@ -0,0 +1,25 @@ +module Pio + module OpenFlow10 + class PacketIn < OpenFlow::Message + # Why is this packet being sent to the controller? + # (enum ofp_packet_in_reason) + class Reason < BinData::Primitive + REASONS = { no_match: 0, action: 1 }.freeze + + uint8 :reason + + def get + REASONS.invert.fetch(reason) + end + + def set(value) + self.reason = REASONS.fetch(value) + end + + def to_bytes + reason.to_hex + end + end + end + end +end diff --git a/lib/pio/open_flow10/packet_out.rb b/lib/pio/open_flow10/packet_out.rb index c012d1c4..416320fc 100644 --- a/lib/pio/open_flow10/packet_out.rb +++ b/lib/pio/open_flow10/packet_out.rb @@ -1,18 +1,17 @@ -require 'pio/open_flow' +require 'pio/open_flow/message' require 'pio/open_flow10/actions' -# Base module. module Pio module OpenFlow10 # OpenFlow 1.0 Packet-Out message class PacketOut < OpenFlow::Message open_flow_header version: 1, - message_type: 13, - message_length: -> { 16 + actions_len + raw_data.length } + type: 13, + length: -> { 16 + actions_length + raw_data.length } uint32 :buffer_id uint16 :in_port - uint16 :actions_len, initial_value: -> { actions.binary.length } - actions :actions, length: -> { actions_len } + uint16 :actions_length, initial_value: -> { actions.binary.length } + actions10 :actions, length: -> { actions_length } rest :raw_data end end diff --git a/lib/pio/open_flow10/phy_port16.rb b/lib/pio/open_flow10/phy_port16.rb index d1602009..65dae42c 100644 --- a/lib/pio/open_flow10/phy_port16.rb +++ b/lib/pio/open_flow10/phy_port16.rb @@ -1,4 +1,5 @@ require 'bindata' +require 'pio/open_flow/flags' require 'pio/type/mac_address' module Pio @@ -7,84 +8,61 @@ module OpenFlow10 class PhyPort16 < BinData::Record extend OpenFlow::Flags - # enum ofp_port_config - flags_32bit :port_config, + endian :big + + uint16 :number + mac_address :mac_address + string :name, length: 16, trim_padding: true + flags_32bit :config, [:port_down, :no_stp, - :no_recv, - :no_recv_stp, + :no_receive, + :no_receive_stp, :no_flood, - :no_fwd, + :no_forward, :no_packet_in] - - # enum ofp_port_state - flags_32bit :port_state, + flags_32bit :state, link_down: 1 << 0, stp_listen: 0 << 8, stp_learn: 1 << 8, stp_forward: 2 << 8, stp_block: 3 << 8 - # enum ofp_port_features - flags_32bit :port_feature, - [:port_10mb_hd, - :port_10mb_fd, - :port_100mb_hd, - :port_100mb_fd, - :port_1gb_hd, - :port_1gb_fd, - :port_10gb_fd, - :port_copper, - :port_fiber, - :port_autoneg, - :port_pause, - :port_pause_asym] - - endian :big - - uint16 :port_no - mac_address :hardware_address - string :name, length: 16, trim_padding: true - port_config :config - port_state :state + define_flags_32bit :port_feature, + [:port_10mb_hd, + :port_10mb_fd, + :port_100mb_hd, + :port_100mb_fd, + :port_1gb_hd, + :port_1gb_fd, + :port_10gb_fd, + :port_copper, + :port_fiber, + :port_autoneg, + :port_pause, + :port_pause_asym] port_feature :curr port_feature :advertised port_feature :supported port_feature :peer - # rubocop:disable MethodLength - def snapshot - super.tap do |ss| - def ss.datapath_id - @datapath_id || fail - end - - def ss.dpid - @datapath_id || fail - end + cattr_reader(:length) { 48 } - def ss.number - port_no - end + attr_accessor :datapath_id + alias dpid datapath_id + alias dpid= datapath_id= - def ss.mac_address - hardware_address - end - - def ss.up? - !down? - end + def up? + !down? + end - def ss.down? - config.include?(:port_down) || state.include?(:link_down) - end + def down? + config.include?(:port_down) || state.include?(:link_down) + end - def ss.local? - port_no == OpenFlow10::Port16.reserved_port_number(:local) - end - end + def local? + number == OpenFlow10::Port16.reserved_port_number(:local) end - # rubocop:enable MethodLength end end end diff --git a/lib/pio/open_flow10/port_stats/request.rb b/lib/pio/open_flow10/port_stats/request.rb index 59c1383e..b77c61ac 100644 --- a/lib/pio/open_flow10/port_stats/request.rb +++ b/lib/pio/open_flow10/port_stats/request.rb @@ -6,9 +6,7 @@ module OpenFlow10 class PortStats # Port Stats Request message class Request < OpenFlow::Message - open_flow_header version: 1, - message_type: 16, - message_length: 20 + open_flow_header version: 1, type: 16, length: 20 stats_type :stats_type, value: -> { :port } uint16 :flags diff --git a/lib/pio/open_flow10/port_status.rb b/lib/pio/open_flow10/port_status.rb index 017c9b96..7cbfb5a8 100644 --- a/lib/pio/open_flow10/port_status.rb +++ b/lib/pio/open_flow10/port_status.rb @@ -1,43 +1,36 @@ +require 'active_support/core_ext/module/delegation' require 'pio/open_flow/message' +require 'pio/open_flow10/phy_port16' +require 'pio/open_flow10/port_status/reason' module Pio module OpenFlow10 # OpenFlow 1.0 Port Status message class PortStatus < OpenFlow::Message - # What changed about the physical port - class Reason < BinData::Primitive - REASONS = { add: 0, delete: 1, modify: 2 } + open_flow_header version: 1, type: 12, + length: -> { header_length + 8 + PhyPort16.length } - uint8 :reason - - def get - REASONS.invert.fetch(reason) - end - - def set(value) - self.reason = REASONS.fetch(value) - end - end - - open_flow_header version: 1, - message_type: 12, - message_length: 10 reason :reason uint56 :padding hide :padding - phy_port16 :desc - - def reason - @format.reason.to_sym - end - - attr_writer :datapath_id - - def desc - @desc ||= @format.desc.snapshot - @desc.instance_variable_set :@datapath_id, @datapath_id - @desc - end + phy_port16 :description + + attr_accessor :datapath_id + alias dpid datapath_id + alias dpid= datapath_id= + + delegate :number, to: :description + delegate :mac_address, to: :description + delegate :name, to: :description + delegate :config, to: :description + delegate :state, to: :description + delegate :curr, to: :description + delegate :advertised, to: :description + delegate :supported, to: :description + delegate :peer, to: :description + delegate :up?, to: :description + delegate :down?, to: :description + delegate :local?, to: :description end end end diff --git a/lib/pio/open_flow10/port_status/reason.rb b/lib/pio/open_flow10/port_status/reason.rb new file mode 100644 index 00000000..07a0ad62 --- /dev/null +++ b/lib/pio/open_flow10/port_status/reason.rb @@ -0,0 +1,21 @@ +module Pio + module OpenFlow10 + # OpenFlow 1.0 Port Status message + class PortStatus < OpenFlow::Message + # What changed about the physical port + class Reason < BinData::Primitive + REASONS = { add: 0, delete: 1, modify: 2 }.freeze + + uint8 :reason + + def get + REASONS.invert.fetch(reason) + end + + def set(value) + self.reason = REASONS.fetch(value) + end + end + end + end +end diff --git a/lib/pio/open_flow10/queue_stats/request.rb b/lib/pio/open_flow10/queue_stats/request.rb index 2896bb7c..32fc3f64 100644 --- a/lib/pio/open_flow10/queue_stats/request.rb +++ b/lib/pio/open_flow10/queue_stats/request.rb @@ -6,9 +6,7 @@ module OpenFlow10 class QueueStats # Queue Stats Request message class Request < OpenFlow::Message - open_flow_header version: 1, - message_type: 16, - message_length: 20 + open_flow_header version: 1, type: 16, length: 20 stats_type :stats_type, value: -> { :queue } uint16 :flags diff --git a/lib/pio/open_flow10/send_out_port.rb b/lib/pio/open_flow10/send_out_port.rb index 4f46b6dc..0e1ce490 100644 --- a/lib/pio/open_flow10/send_out_port.rb +++ b/lib/pio/open_flow10/send_out_port.rb @@ -21,8 +21,8 @@ def initialize(user_options) end max_length = options[:max_length] if max_length && !max_length.unsigned_16bit? - fail(ArgumentError, - 'The max_length should be an unsigned 16bit integer.') + raise(ArgumentError, + 'The max_length should be an unsigned 16bit integer.') end super(options) end diff --git a/lib/pio/open_flow10/set_tos.rb b/lib/pio/open_flow10/set_tos.rb index 796a9b33..8db1853d 100644 --- a/lib/pio/open_flow10/set_tos.rb +++ b/lib/pio/open_flow10/set_tos.rb @@ -14,8 +14,8 @@ def initialize(type_of_service) # tos (IP ToS) value consists of 8 bits, of which only the # 6 high-order bits belong to DSCP, the 2 low-order bits must # be zero. - unless type_of_service.unsigned_8bit? && type_of_service % 4 == 0 - fail ArgumentError, 'Invalid type_of_service (ToS) value.' + unless type_of_service.unsigned_8bit? && (type_of_service % 4).zero? + raise ArgumentError, 'Invalid type_of_service (ToS) value.' end super(type_of_service: type_of_service) end diff --git a/lib/pio/open_flow10/set_transport_port.rb b/lib/pio/open_flow10/set_transport_port.rb index 80170010..6cd560e7 100644 --- a/lib/pio/open_flow10/set_transport_port.rb +++ b/lib/pio/open_flow10/set_transport_port.rb @@ -13,7 +13,8 @@ class SetTransportSourcePort < OpenFlow::Action def initialize(number) port = number.to_i unless port.unsigned_16bit? - fail ArgumentError, 'TCP/UDP port must be an unsigned 16-bit integer.' + raise ArgumentError, + 'TCP/UDP port must be an unsigned 16-bit integer.' end super(port: port) rescue NoMethodError @@ -31,7 +32,8 @@ class SetTransportDestinationPort < OpenFlow::Action def initialize(number) port = number.to_i unless port.unsigned_16bit? - fail ArgumentError, 'TCP/UDP port must be an unsigned 16-bit integer.' + raise ArgumentError, + 'TCP/UDP port must be an unsigned 16-bit integer.' end super(port: port) rescue NoMethodError diff --git a/lib/pio/open_flow10/set_vlan_priority.rb b/lib/pio/open_flow10/set_vlan_priority.rb index 3bddbddc..b4e5e600 100644 --- a/lib/pio/open_flow10/set_vlan_priority.rb +++ b/lib/pio/open_flow10/set_vlan_priority.rb @@ -12,7 +12,7 @@ class SetVlanPriority < OpenFlow::Action def initialize(number) priority = number.to_i if priority < 0 || priority > 7 - fail ArgumentError, 'VLAN priority must be between 0 and 7 inclusive' + raise ArgumentError, 'VLAN priority must be between 0 and 7 inclusive' end super(vlan_priority: priority) rescue NoMethodError diff --git a/lib/pio/open_flow10/set_vlan_vid.rb b/lib/pio/open_flow10/set_vlan_vid.rb index 8f506ac7..c8c34755 100644 --- a/lib/pio/open_flow10/set_vlan_vid.rb +++ b/lib/pio/open_flow10/set_vlan_vid.rb @@ -12,7 +12,7 @@ class SetVlanVid < OpenFlow::Action def initialize(number) vlan_id = number.to_i unless vlan_id >= 1 && vlan_id <= 4095 - fail ArgumentError, 'VLAN ID must be between 1 and 4095 inclusive' + raise ArgumentError, 'VLAN ID must be between 1 and 4095 inclusive' end super(vlan_id: vlan_id) rescue NoMethodError diff --git a/lib/pio/open_flow10/stats_reply.rb b/lib/pio/open_flow10/stats_reply.rb index 11bb07d4..76f64221 100644 --- a/lib/pio/open_flow10/stats_reply.rb +++ b/lib/pio/open_flow10/stats_reply.rb @@ -7,21 +7,18 @@ module Pio module OpenFlow10 class Stats # Stats reply parser. - class Reply - TYPES = { + class Reply < OpenFlow::Message + open_flow_header version: 1, type: 17, length: 10 + stats_type :stats_type + + TYPE = { description: OpenFlow10::DescriptionStats::Reply, flow: OpenFlow10::FlowStats::Reply, aggregate: OpenFlow10::AggregateStats::Reply - } - - # Stats reply format. - class Format < OpenFlow::Message - open_flow_header version: 1, message_type: 17, message_length: 10 - stats_type :stats_type - end + }.freeze def self.read(binary) - TYPES.fetch(Format.read(binary).stats_type.to_sym).read(binary) + TYPE.fetch(Format.read(binary).stats_type.to_sym).read(binary) rescue KeyError raise "Unknown stats type: #{stats_type}" end diff --git a/lib/pio/open_flow10/stats_request.rb b/lib/pio/open_flow10/stats_request.rb index 8ec47469..3c3d3150 100644 --- a/lib/pio/open_flow10/stats_request.rb +++ b/lib/pio/open_flow10/stats_request.rb @@ -1,28 +1,29 @@ -require 'pio/open_flow10/table_stats/request' -require 'pio/open_flow10/port_stats/request' require 'pio/open_flow/message' +require 'pio/open_flow10/aggregate_stats/request' +require 'pio/open_flow10/flow_stats/request' +require 'pio/open_flow10/port_stats/request' +require 'pio/open_flow10/queue_stats/request' +require 'pio/open_flow10/table_stats/request' module Pio module OpenFlow10 class Stats # Stats request parser. - class Request - TYPES = { + class Request < OpenFlow::Message + open_flow_header version: 1, type: 16, length: 10 + stats_type :stats_type + + TYPE = { + aggregate: OpenFlow10::AggregateStats::Request, description: OpenFlow10::DescriptionStats::Request, flow: OpenFlow10::FlowStats::Request, - aggregate: OpenFlow10::AggregateStats::Request, - table: OpenFlow10::TableStats::Request, - port: OpenFlow10::PortStats::Request - } - - # Stats request format. - class Format < OpenFlow::Message - open_flow_header version: 1, message_type: 16, message_length: 10 - stats_type :stats_type - end + port: OpenFlow10::PortStats::Request, + queue: OpenFlow10::QueueStats::Request, + table: OpenFlow10::TableStats::Request + }.freeze def self.read(binary) - TYPES.fetch(Format.read(binary).stats_type.to_sym).read(binary) + TYPE.fetch(Format.read(binary).stats_type.to_sym).read(binary) rescue KeyError raise "Unknown stats type: #{stats_type}" end diff --git a/lib/pio/open_flow10/stats_type.rb b/lib/pio/open_flow10/stats_type.rb index fa48b2a2..22603c4d 100644 --- a/lib/pio/open_flow10/stats_type.rb +++ b/lib/pio/open_flow10/stats_type.rb @@ -10,7 +10,7 @@ class StatsType < BinData::Primitive port: 4, queue: 5, vendor: 0xffff - } + }.freeze endian :big uint16 :command diff --git a/lib/pio/open_flow10/table_stats/request.rb b/lib/pio/open_flow10/table_stats/request.rb index 23056f4c..af2c2793 100644 --- a/lib/pio/open_flow10/table_stats/request.rb +++ b/lib/pio/open_flow10/table_stats/request.rb @@ -7,9 +7,7 @@ module OpenFlow10 module TableStats # OpenFlow 1.0 Table Stats Request message class Request < OpenFlow::Message - open_flow_header version: 1, - message_type: 16, - message_length: 12 + open_flow_header version: 1, type: 16, length: 12 stats_type :stats_type, value: -> { :table } uint16 :flags string :body, value: '' diff --git a/lib/pio/open_flow10/vendor_action.rb b/lib/pio/open_flow10/vendor_action.rb index ec35e6e3..cc039859 100644 --- a/lib/pio/open_flow10/vendor_action.rb +++ b/lib/pio/open_flow10/vendor_action.rb @@ -2,32 +2,34 @@ require 'forwardable' module Pio - # Vendor defined action - class VendorAction - # OpenFlow 1.0 OFPAT_VENDOR action format. - class Format < BinData::Record - endian :big + module OpenFlow10 + # Vendor defined action + class VendorAction + # OpenFlow 1.0 OFPAT_VENDOR action format. + class Format < BinData::Record + endian :big - uint16 :action_type, value: 0xffff - uint16 :action_length, value: 8 - uint32 :vendor - end + uint16 :action_type, value: 0xffff + uint16 :action_length, value: 8 + uint32 :vendor + end - def self.read(raw_data) - allocate.tap do |strip_vlan| - strip_vlan.instance_variable_set :@format, Format.read(raw_data) + def self.read(raw_data) + allocate.tap do |strip_vlan| + strip_vlan.instance_variable_set :@format, Format.read(raw_data) + end end - end - extend Forwardable + extend Forwardable - def_delegators :@format, :action_type - def_delegator :@format, :action_length, :length - def_delegators :@format, :vendor - def_delegator :@format, :to_binary_s, :to_binary + def_delegators :@format, :action_type + def_delegator :@format, :action_length, :length + def_delegators :@format, :vendor + def_delegator :@format, :to_binary_s, :to_binary - def initialize(vendor) - @format = Format.new(vendor: vendor) + def initialize(vendor) + @format = Format.new(vendor: vendor) + end end end end diff --git a/lib/pio/open_flow13.rb b/lib/pio/open_flow13.rb index 97b878ae..730ac475 100644 --- a/lib/pio/open_flow13.rb +++ b/lib/pio/open_flow13.rb @@ -15,14 +15,21 @@ # Actions require 'pio/open_flow/nicira_resubmit' require 'pio/open_flow/nicira_resubmit_table' +require 'pio/open_flow13/copy_ttl_inwards' +require 'pio/open_flow13/copy_ttl_outwards' +require 'pio/open_flow13/decrement_ip_ttl' require 'pio/open_flow13/nicira_reg_load' require 'pio/open_flow13/nicira_reg_move' require 'pio/open_flow13/nicira_send_out_port' +require 'pio/open_flow13/nicira_stack_pop' +require 'pio/open_flow13/nicira_stack_push' +require 'pio/open_flow13/nicira_conjunction' require 'pio/open_flow13/send_out_port' require 'pio/open_flow13/set_arp_operation' require 'pio/open_flow13/set_arp_sender_hardware_address' require 'pio/open_flow13/set_arp_sender_protocol_address' require 'pio/open_flow13/set_destination_mac_address' +require 'pio/open_flow13/set_ip_ttl' require 'pio/open_flow13/set_metadata' require 'pio/open_flow13/set_source_mac_address' diff --git a/lib/pio/open_flow13/actions.rb b/lib/pio/open_flow13/actions.rb index db605449..4e606d29 100644 --- a/lib/pio/open_flow13/actions.rb +++ b/lib/pio/open_flow13/actions.rb @@ -1,47 +1,49 @@ require 'bindata' module Pio - # Actions not yet implemented. - class UnsupportedAction < BinData::Record - endian :big + module OpenFlow13 + # Actions not yet implemented. + class UnsupportedAction < BinData::Record + endian :big - uint16 :action_type - uint16 :action_length - string :body, length: -> { action_length - 4 } + uint16 :action_type + uint16 :action_length + string :body, length: -> { action_length - 4 } - def to_binary - to_binary_s + def to_binary + to_binary_s + end end - end - # Actions list of actions-apply instruction. - class Actions < BinData::Primitive - mandatory_parameter :length + # Actions list of actions-apply instruction. + class Actions13 < BinData::Primitive + mandatory_parameter :length - endian :big + endian :big - string :binary, read_length: :length + string :binary, read_length: :length - def set(actions) - self.binary = Array(actions).map(&:to_binary).join - end + def set(actions) + self.binary = Array(actions).map(&:to_binary).join + end - # rubocop:disable MethodLength - def get - actions = [] - tmp = binary - while tmp.length > 0 - action = case BinData::Uint16be.read(tmp) - when 0 - SendOutPort.read(tmp) - else - UnsupportedAction.read(tmp) - end - tmp = tmp[action.action_length..-1] - actions << action + # rubocop:disable MethodLength + def get + actions = [] + tmp = binary + until tmp.empty? + action = case BinData::Uint16be.read(tmp) + when 0 + OpenFlow13::SendOutPort.read(tmp) + else + UnsupportedAction.read(tmp) + end + tmp = tmp[action.action_length..-1] + actions << action + end + actions end - actions + # rubocop:enable MethodLength end - # rubocop:enable MethodLength end end diff --git a/lib/pio/open_flow13/apply.rb b/lib/pio/open_flow13/apply.rb index 1833f88f..1e7e7cbc 100644 --- a/lib/pio/open_flow13/apply.rb +++ b/lib/pio/open_flow13/apply.rb @@ -1,36 +1,39 @@ require 'bindata' require 'forwardable' +require 'pio/open_flow/instruction' require 'pio/open_flow13/actions' module Pio - # An instruction to apply a list of actions to a packet in-order. - class Apply - # OpenFlow 1.3.4 OFPIT_APPLY_ACTIONS instruction format. - class Format < BinData::Record - endian :big + module OpenFlow13 + # An instruction to apply a list of actions to a packet in-order. + class Apply < OpenFlow::Instruction + # OpenFlow 1.3.4 OFPIT_APPLY_ACTIONS instruction format. + class Format < BinData::Record + endian :big - uint16 :instruction_type, value: 4 - uint16 :instruction_length, - initial_value: -> { 8 + actions.binary.length } - string :padding, length: 4 - actions :actions, length: -> { instruction_length - 8 } - end + uint16 :instruction_type, value: 4 + uint16 :instruction_length, + initial_value: -> { 8 + actions.binary.length } + string :padding, length: 4 + actions13 :actions, length: -> { instruction_length - 8 } + end - def self.read(raw_data) - allocate.tap do |apply| - apply.instance_variable_set :@format, Format.read(raw_data) + def self.read(raw_data) + allocate.tap do |apply| + apply.instance_variable_set :@format, Format.read(raw_data) + end end - end - extend Forwardable + extend Forwardable - def_delegators :@format, :instruction_type - def_delegators :@format, :instruction_length - def_delegators :@format, :actions - def_delegators :@format, :to_binary_s + def_delegators :@format, :instruction_type + def_delegators :@format, :instruction_length + def_delegators :@format, :actions + def_delegators :@format, :to_binary_s - def initialize(actions = []) - @format = Format.new(actions: actions) + def initialize(actions = []) + @format = Format.new(actions: actions) + end end end end diff --git a/lib/pio/open_flow13/buffer_id.rb b/lib/pio/open_flow13/buffer_id.rb deleted file mode 100644 index 65aa4164..00000000 --- a/lib/pio/open_flow13/buffer_id.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Pio - # Buffered packet to apply to, or :no_buffer. - class BufferId < BinData::Primitive - NO_BUFFER = 0xffffffff - - endian :big - uint32 :buffer_id, initial_value: NO_BUFFER - - def get - (buffer_id == NO_BUFFER) ? :no_buffer : buffer_id - end - - def set(value) - self.buffer_id = (value == :no_buffer ? NO_BUFFER : value) - end - end -end diff --git a/lib/pio/open_flow13/copy_ttl_inwards.rb b/lib/pio/open_flow13/copy_ttl_inwards.rb new file mode 100644 index 00000000..82b57129 --- /dev/null +++ b/lib/pio/open_flow13/copy_ttl_inwards.rb @@ -0,0 +1,15 @@ +require 'pio/open_flow/action' + +module Pio + module OpenFlow13 + # Copies TTL "inwards" -- from outermost to next-to-outermost + class CopyTtlInwards < OpenFlow::Action + action_header action_type: 12, action_length: 8 + string :padding, length: 4 + + def initialize + super({}) + end + end + end +end diff --git a/lib/pio/open_flow13/copy_ttl_outwards.rb b/lib/pio/open_flow13/copy_ttl_outwards.rb new file mode 100644 index 00000000..80fd9efa --- /dev/null +++ b/lib/pio/open_flow13/copy_ttl_outwards.rb @@ -0,0 +1,15 @@ +require 'pio/open_flow/action' + +module Pio + module OpenFlow13 + # Copies TTL "outwards" -- from next-to-outermost to outermost + class CopyTtlOutwards < OpenFlow::Action + action_header action_type: 11, action_length: 8 + string :padding, length: 4 + + def initialize + super({}) + end + end + end +end diff --git a/lib/pio/open_flow13/decrement_ip_ttl.rb b/lib/pio/open_flow13/decrement_ip_ttl.rb new file mode 100644 index 00000000..32327f4f --- /dev/null +++ b/lib/pio/open_flow13/decrement_ip_ttl.rb @@ -0,0 +1,15 @@ +require 'pio/open_flow/action' + +module Pio + module OpenFlow13 + # Decrements IP TTL + class DecrementIpTtl < OpenFlow::Action + action_header action_type: 24, action_length: 8 + string :padding, length: 4 + + def initialize + super({}) + end + end + end +end diff --git a/lib/pio/open_flow13/echo/reply.rb b/lib/pio/open_flow13/echo/reply.rb index 6eb71bfa..e06e1f9f 100644 --- a/lib/pio/open_flow13/echo/reply.rb +++ b/lib/pio/open_flow13/echo/reply.rb @@ -5,10 +5,10 @@ module OpenFlow13 module Echo # OpenFlow 1.3 Echo Reply message. class Reply < OpenFlow::Message - open_flow_header version: 4, message_type: 3 - string :body, read_length: -> { message_length - 8 } + open_flow_header version: 4, type: 3 + string :body, read_length: -> { length - 8 } - alias_method :user_data, :body + alias user_data body end end end diff --git a/lib/pio/open_flow13/echo/request.rb b/lib/pio/open_flow13/echo/request.rb index a0cd834a..0e42c211 100644 --- a/lib/pio/open_flow13/echo/request.rb +++ b/lib/pio/open_flow13/echo/request.rb @@ -5,10 +5,10 @@ module OpenFlow13 module Echo # OpenFlow 1.3 Echo Request message. class Request < OpenFlow::Message - open_flow_header version: 4, message_type: 2 - string :body, read_length: -> { message_length - 8 } + open_flow_header version: 4, type: 2 + string :body, read_length: -> { length - header_length } - alias_method :user_data, :body + alias user_data body end end end diff --git a/lib/pio/open_flow13/error.rb b/lib/pio/open_flow13/error.rb index c27e3705..af44cde9 100644 --- a/lib/pio/open_flow13/error.rb +++ b/lib/pio/open_flow13/error.rb @@ -1,33 +1,22 @@ +require 'pio/open_flow/error_message' +require 'pio/open_flow13/error/bad_request' require 'pio/open_flow13/error/error_type13' +require 'pio/open_flow13/error/hello_failed' module Pio module OpenFlow13 # Error message parser module Error + mattr_reader(:type) { 1 } + + extend OpenFlow::ErrorMessage + # Error message body parser. class BodyParser < BinData::Record endian :big error_type13 :error_type uint16 :error_code end - - # rubocop:disable MethodLength - def self.read(binary) - body = OpenFlowHeaderParser.read(binary).body - error = BodyParser.read(body).snapshot - klass = case error.error_type - when :hello_failed - HelloFailed - when :bad_request - BadRequest - else - # Not implemented yet - fail 'Unknown error message '\ - "(type=#{error.error_type}, code=#{error.error_code})" - end - klass.read binary - end - # rubocop:enable MethodLength end end end diff --git a/lib/pio/open_flow13/error/bad_request.rb b/lib/pio/open_flow13/error/bad_request.rb index c5d5c6cc..bc93998a 100644 --- a/lib/pio/open_flow13/error/bad_request.rb +++ b/lib/pio/open_flow13/error/bad_request.rb @@ -1,3 +1,4 @@ +require 'pio/open_flow/error_message' require 'pio/open_flow/message' require 'pio/open_flow13/error/error_type13' @@ -23,7 +24,7 @@ class BadRequestCode < BinData::Primitive bad_port: 11, bad_packet: 12, multipart_buffer_overflow: 13 - } + }.freeze endian :big uint16 :error_code @@ -38,8 +39,8 @@ def set(value) end open_flow_header version: 4, - message_type: 1, - message_length: -> { 12 + raw_data.length } + type: OpenFlow::ErrorMessage.type, + length: -> { 12 + raw_data.length } error_type13 :error_type, value: -> { :bad_request } bad_request_code :error_code rest :raw_data diff --git a/lib/pio/open_flow13/error/error_type13.rb b/lib/pio/open_flow13/error/error_type13.rb index ed17dc9b..ff05b9d1 100644 --- a/lib/pio/open_flow13/error/error_type13.rb +++ b/lib/pio/open_flow13/error/error_type13.rb @@ -19,7 +19,7 @@ class ErrorType13 < BinData::Primitive meter_mod_failed: 12, table_features_failed: 13, experimenter: 0xffff - } + }.freeze endian :big uint16 :error_type diff --git a/lib/pio/open_flow13/error/hello_failed.rb b/lib/pio/open_flow13/error/hello_failed.rb index 4881af85..9d954935 100644 --- a/lib/pio/open_flow13/error/hello_failed.rb +++ b/lib/pio/open_flow13/error/hello_failed.rb @@ -1,3 +1,4 @@ +require 'pio/open_flow/error_message' require 'pio/open_flow/hello_failed_code' require 'pio/open_flow/message' require 'pio/open_flow13/error/error_type13' @@ -8,8 +9,8 @@ module Error # Hello Failed error message class HelloFailed < OpenFlow::Message open_flow_header version: 4, - message_type: 1, - message_length: -> { 12 + description.length } + type: OpenFlow::ErrorMessage.type, + length: -> { 12 + description.length } error_type13 :error_type hello_failed_code :error_code rest :description diff --git a/lib/pio/open_flow13/features/reply.rb b/lib/pio/open_flow13/features/reply.rb index d3e81655..e1521793 100644 --- a/lib/pio/open_flow13/features/reply.rb +++ b/lib/pio/open_flow13/features/reply.rb @@ -1,3 +1,4 @@ +require 'pio/open_flow/datapath_id' require 'pio/open_flow/message' module Pio @@ -6,8 +7,14 @@ module OpenFlow13 class Features # Features Reply message. class Reply < OpenFlow::Message - extend OpenFlow::Flags - + open_flow_header version: 4, type: 6, length: 32 + datapath_id :datapath_id + alias dpid datapath_id + uint32 :n_buffers + uint8 :n_tables + uint8 :auxiliary_id + uint16 :padding + hide :padding flags_32bit(:capabilities, [:flow_stats, :table_stats, @@ -18,21 +25,7 @@ class Reply < OpenFlow::Message :queue_stats, :NOT_USED, :port_blocked]) - - open_flow_header version: 4, message_type: 6, message_length: 32 - datapath_id :datapath_id - uint32 :n_buffers - uint8 :n_tables - uint8 :auxiliary_id - uint16 :padding - hide :padding - capabilities :capabilities uint32 :reserved - - def datapath_id - @format.datapath_id.to_i - end - alias_method :dpid, :datapath_id end end end diff --git a/lib/pio/open_flow13/features/request.rb b/lib/pio/open_flow13/features/request.rb index ebfa4c5e..0e6a29ae 100644 --- a/lib/pio/open_flow13/features/request.rb +++ b/lib/pio/open_flow13/features/request.rb @@ -6,7 +6,7 @@ module OpenFlow13 class Features # Features Request message. class Request < OpenFlow::Message - open_flow_header version: 4, message_type: 5 + open_flow_header version: 4, type: 5 string :body, value: '' end end diff --git a/lib/pio/open_flow13/flow_mod.rb b/lib/pio/open_flow13/flow_mod.rb index 375a912d..4aa11250 100644 --- a/lib/pio/open_flow13/flow_mod.rb +++ b/lib/pio/open_flow13/flow_mod.rb @@ -1,5 +1,5 @@ require 'pio/open_flow' -require 'pio/open_flow13/buffer_id' +require 'pio/open_flow/buffer_id' require 'pio/open_flow13/match' module Pio @@ -14,7 +14,7 @@ class Command < BinData::Primitive modify_strict: 2, delete: 3, delete_strict: 4 - } + }.freeze uint8 :command @@ -36,7 +36,7 @@ class OutPort < BinData::Primitive uint32 :out_port, initial_value: ANY def get - (out_port == ANY) ? :any : out_port + out_port == ANY ? :any : out_port end def set(value) @@ -53,7 +53,7 @@ class OutGroup < BinData::Primitive uint32 :out_group, initial_value: ANY def get - (out_group == ANY) ? :any : out_group + out_group == ANY ? :any : out_group end def set(value) @@ -76,7 +76,7 @@ def set(instructions) def get list = [] tmp = instructions - while tmp.length > 0 + until tmp.empty? instruction_type = BinData::Uint16be.read(tmp) instruction = case instruction_type when 1 @@ -88,7 +88,7 @@ def get when 6 Meter.read(tmp) else - fail "Unsupported instruction #{instruction_type}" + raise "Unsupported instruction #{instruction_type}" end tmp = tmp[instruction.instruction_length..-1] list << instruction @@ -102,18 +102,8 @@ def length end end - extend OpenFlow::Flags - - flags_16bit :flags, - [:send_flow_rem, - :check_overwrap, - :reset_counts, - :no_packet_counts, - :no_byte_counts] - - open_flow_header(version: 4, - message_type: 14, - message_length: lambda do + open_flow_header(version: 4, type: 14, + length: lambda do 48 + match.length + instructions.length end) uint64 :cookie @@ -126,7 +116,12 @@ def length buffer_id :buffer_id out_port :out_port out_group :out_group - flags :flags + flags_16bit :flags, + [:send_flow_rem, + :check_overwrap, + :reset_counts, + :no_packet_counts, + :no_byte_counts] string :padding, length: 2 hide :padding oxm :match diff --git a/lib/pio/open_flow13/goto_table.rb b/lib/pio/open_flow13/goto_table.rb index 71a2ff12..d6e61f92 100644 --- a/lib/pio/open_flow13/goto_table.rb +++ b/lib/pio/open_flow13/goto_table.rb @@ -1,37 +1,39 @@ require 'bindata' require 'forwardable' +require 'pio/open_flow/instruction' -# Base module. module Pio - # Resubmit to the table_id - class GotoTable - # OpenFlow 1.3.4 OFPIT_GOTO_TABLE instruction format - class Format < BinData::Record - endian :big + module OpenFlow13 + # Resubmit to the table_id + class GotoTable < OpenFlow::Instruction + # OpenFlow 1.3.4 OFPIT_GOTO_TABLE instruction format + class Format < BinData::Record + endian :big - uint16 :instruction_type, value: 1 - uint16 :instruction_length, value: 8 - uint8 :table_id - bit24 :padding - hide :padding - end + uint16 :instruction_type, value: 1 + uint16 :instruction_length, value: 8 + uint8 :table_id + bit24 :padding + hide :padding + end - def self.read(raw_data) - allocate.tap do |goto_table| - goto_table.instance_variable_set :@format, Format.read(raw_data) + def self.read(raw_data) + allocate.tap do |goto_table| + goto_table.instance_variable_set :@format, Format.read(raw_data) + end end - end - extend Forwardable + extend Forwardable - def_delegators :@format, :instruction_type - def_delegators :@format, :instruction_length - def_delegators :@format, :table_id - def_delegators :@format, :to_binary_s - def_delegator :@format, :to_binary_s, :to_binary + def_delegators :@format, :instruction_type + def_delegators :@format, :instruction_length + def_delegators :@format, :table_id + def_delegators :@format, :to_binary_s + def_delegator :@format, :to_binary_s, :to_binary - def initialize(table_id) - @format = Format.new(table_id: table_id) + def initialize(table_id) + @format = Format.new(table_id: table_id) + end end end end diff --git a/lib/pio/open_flow13/hello.rb b/lib/pio/open_flow13/hello.rb index f4c4b76b..949d7f35 100644 --- a/lib/pio/open_flow13/hello.rb +++ b/lib/pio/open_flow13/hello.rb @@ -41,8 +41,8 @@ def length end end - open_flow_header version: 4, message_type: 0 - body :body + open_flow_header version: 4, type: 0 + body :body, read_length: -> { length - header_length } def elements body.elements @@ -68,11 +68,10 @@ def version_bitmap end def initialize(user_options = {}) - validate_user_options user_options body_options = { elements: [{ element_type: VERSION_BITMAP, element_length: 8, element_value: 0b10000 }] } - @format = Format.new(user_options.merge(body: body_options)) + super user_options.merge(body: body_options) end end end diff --git a/lib/pio/open_flow13/match.rb b/lib/pio/open_flow13/match.rb index 46340793..70116490 100644 --- a/lib/pio/open_flow13/match.rb +++ b/lib/pio/open_flow13/match.rb @@ -1,4 +1,5 @@ require 'bindata' +require 'pio/open_flow/flow_match' require 'pio/type/ip_address' require 'pio/type/ipv6_address' require 'pio/type/mac_address' @@ -11,7 +12,7 @@ module OpenFlow13 MATCH_TYPE_OXM = 1 # OpenFlow eXtensible Match (OXM) - class Match + class Match < OpenFlow::FlowMatch # OFPXMC_NXM_1 TLV value class NiciraMatchExtensionValue < BinData::Record OXM_CLASS = 0x1 @@ -22,6 +23,11 @@ class OpenFlowBasicValue < BinData::Record OXM_CLASS = 0x8000 end + # OFPXMC_PACKET_REGS TLV value + class PacketRegistersValue < BinData::Record + OXM_CLASS = 0x8001 + end + # OFPXMC_EXPERIMENTER TLV value class ExperimenterValue < BinData::Record OXM_CLASS = 0xFFFF @@ -759,6 +765,105 @@ def length end end + # OXM_PACKET_REG0 match field + class PacketReg0 < PacketRegistersValue + OXM_FIELD = 0 + + endian :big + + uint64 :packet_reg0 + + def length + 8 + end + end + + # Masked OXM_PACKET_REG0 match field + class MaskedPacketReg0 < PacketRegistersValue + endian :big + + uint64 :packet_reg0 + uint64 :packet_reg0_mask + + def length + 16 + end + end + # OXM_PACKET_REG1 match field + class PacketReg1 < PacketRegistersValue + OXM_FIELD = 1 + + endian :big + + uint64 :packet_reg1 + + def length + 8 + end + end + + # Masked OXM_PACKET_REG1 match field + class MaskedPacketReg1 < PacketRegistersValue + endian :big + + uint64 :packet_reg1 + uint64 :packet_reg1_mask + + def length + 16 + end + end + + # OXM_PACKET_REG2 match field + class PacketReg2 < PacketRegistersValue + OXM_FIELD = 2 + + endian :big + + uint64 :packet_reg2 + + def length + 8 + end + end + + # Masked OXM_PACKET_REG2 match field + class MaskedPacketReg2 < PacketRegistersValue + endian :big + + uint64 :packet_reg2 + uint64 :packet_reg2_mask + + def length + 16 + end + end + + # OXM_PACKET_REG3 match field + class PacketReg3 < PacketRegistersValue + OXM_FIELD = 3 + + endian :big + + uint64 :packet_reg3 + + def length + 8 + end + end + + # Masked OXM_PACKET_REG3 match field + class MaskedPacketReg3 < PacketRegistersValue + endian :big + + uint64 :packet_reg3 + uint64 :packet_reg3_mask + + def length + 16 + end + end + # OXM format class Oxm < BinData::Record # Experimenter part, data will use oxm_length @@ -840,7 +945,7 @@ def choose_tlv_value when Reg7::OXM_FIELD masked? ? MaskedReg7 : Reg7 else - fail "Unknown OXM field value: #{oxm_field}" + raise "Unknown OXM field value: #{oxm_field}" end end # rubocop:enable AbcSize @@ -998,7 +1103,7 @@ def choose_tlv_value when TunnelId::OXM_FIELD masked? ? MaskedTunnelId : TunnelId else - fail "Unknown OXM field value: #{oxm_field}" + raise "Unknown OXM field value: #{oxm_field}" end end # rubocop:enable MethodLength @@ -1008,6 +1113,58 @@ def choose_tlv_value end # rubocop:enable MethodLength + # Packet Register match field. + class PacketRegisters < BinData::Record + endian :big + + bit7 :oxm_field + bit1 :oxm_hasmask + uint8 :oxm_length, value: -> { tlv_value.length } + choice :tlv_value, selection: :choose_tlv_value do + packet_reg0 PacketReg0 + masked_packet_reg0 MaskedPacketReg0 + packet_reg1 PacketReg1 + masked_packet_reg1 MaskedPacketReg1 + packet_reg2 PacketReg2 + masked_packet_reg2 MaskedPacketReg2 + packet_reg3 PacketReg3 + masked_packet_reg3 MaskedPacketReg3 + end + + def length + tlv_value.length + 2 + end + + def masked? + oxm_hasmask == 1 + end + + def method_missing(method, *args, &block) + tlv_value.__send__ method, *args, &block + end + + private + + # rubocop:disable CyclomaticComplexity + # rubocop:disable MethodLength + def choose_tlv_value + case oxm_field + when PacketReg0::OXM_FIELD + masked? ? MaskedPacketReg0 : PacketReg0 + when PacketReg1::OXM_FIELD + masked? ? MaskedPacketReg1 : PacketReg1 + when PacketReg2::OXM_FIELD + masked? ? MaskedPacketReg2 : PacketReg2 + when PacketReg3::OXM_FIELD + masked? ? MaskedPacketReg3 : PacketReg3 + else + raise "Unknown OXM field value: #{oxm_field}" + end + end + # rubocop:enable CyclomaticComplexity + # rubocop:enable MethodLength + end + # OXM match field. class MatchField < BinData::Record endian :big @@ -1017,6 +1174,7 @@ class MatchField < BinData::Record NiciraMatchExtension NiciraMatchExtensionValue::OXM_CLASS OpenflowBasic OpenFlowBasicValue::OXM_CLASS Experimenter ExperimenterValue::OXM_CLASS + PacketRegisters PacketRegistersValue::OXM_CLASS end def oxm_field @@ -1041,8 +1199,10 @@ def method_missing(method, *args, &block) return class_payload.tlv_value.__send__(method, *args, &block) when ExperimenterValue::OXM_CLASS return class_payload.__send__(method, *args, &block) + when PacketRegistersValue::OXM_CLASS + return class_payload.__send__(method, *args, &block) else - fail NoMethodError, method.to_s + raise NoMethodError, method.to_s end end end @@ -1062,18 +1222,21 @@ def length match_length + padding_length end + # rubocop:disable AbcSize # rubocop:disable Next # rubocop:disable LineLength def method_missing(method, *args, &block) match_fields.each do |each| - if each.oxm_class == OpenFlowBasicValue::OXM_CLASS || each.oxm_class == NiciraMatchExtensionValue::OXM_CLASS + if each.oxm_class == OpenFlowBasicValue::OXM_CLASS || each.oxm_class == NiciraMatchExtensionValue::OXM_CLASS || each.oxm_class == PacketRegistersValue::OXM_CLASS next unless each.class_payload.tlv_value.respond_to?(method) return each.class_payload.tlv_value.__send__( - method, *args, &block) + method, *args, &block + ) end end - fail NoMethodError, method.to_s + raise NoMethodError, method.to_s end + # rubocop:enable AbcSize # rubocop:enable Next # rubocop:enable LineLength @@ -1084,7 +1247,7 @@ def tlv_length_left end def tlv_total_length - if match_fields.size > 0 + if !match_fields.empty? match_fields.map(&:length).inject(&:+) else 0 @@ -1121,7 +1284,8 @@ def initialize(user_attrs) :arp_sender_protocol_address, :arp_target_protocol_address, :arp_sender_hardware_address, :arp_target_hardware_address, :ipv6_source_address, :ipv6_destination_address, :tunnel_id, - :reg0, :reg1, :reg2, :reg3, :reg4, :reg5, :reg6, :reg7].each do |each| + :reg0, :reg1, :reg2, :reg3, :reg4, :reg5, :reg6, :reg7, + :packet_reg0, :packet_reg1, :packet_reg2, :packet_reg3].each do |each| next unless user_attrs.key?(each) klass = Match.const_get(each.to_s.split('_').map(&:capitalize).join) mask_key = "#{each}_mask".to_sym diff --git a/lib/pio/open_flow13/meter.rb b/lib/pio/open_flow13/meter.rb index eed315fc..3fd2ce3d 100644 --- a/lib/pio/open_flow13/meter.rb +++ b/lib/pio/open_flow13/meter.rb @@ -1,33 +1,36 @@ require 'forwardable' +require 'pio/open_flow/instruction' # Base module. module Pio - # Apply meter (rate limiter) - class Meter - # OpenFlow 1.3.4 OFPIT_METER instruction format - class Format < BinData::Record - endian :big + module OpenFlow13 + # Apply meter (rate limiter) + class Meter < OpenFlow::Instruction + # OpenFlow 1.3.4 OFPIT_METER instruction format + class Format < BinData::Record + endian :big - uint16 :instruction_type, value: 6 - uint16 :instruction_length, value: 8 - uint32 :meter_id - end + uint16 :instruction_type, value: 6 + uint16 :instruction_length, value: 8 + uint32 :meter_id + end - def self.read(raw_data) - allocate.tap do |meter| - meter.instance_variable_set :@format, Format.read(raw_data) + def self.read(raw_data) + allocate.tap do |meter| + meter.instance_variable_set :@format, Format.read(raw_data) + end end - end - extend Forwardable + extend Forwardable - def_delegators :@format, :instruction_type - def_delegators :@format, :instruction_length - def_delegators :@format, :meter_id - def_delegators :@format, :to_binary_s + def_delegators :@format, :instruction_type + def_delegators :@format, :instruction_length + def_delegators :@format, :meter_id + def_delegators :@format, :to_binary_s - def initialize(meter_id) - @format = Format.new(meter_id: meter_id) + def initialize(meter_id) + @format = Format.new(meter_id: meter_id) + end end end end diff --git a/lib/pio/open_flow13/nicira_conjunction.rb b/lib/pio/open_flow13/nicira_conjunction.rb new file mode 100644 index 00000000..286f3224 --- /dev/null +++ b/lib/pio/open_flow13/nicira_conjunction.rb @@ -0,0 +1,26 @@ +require 'pio/open_flow/nicira_action' + +module Pio + module OpenFlow13 + # NXAST_CONJUNCTION action + class NiciraConjunction < OpenFlow::NiciraAction + nicira_action_header action_type: 0xffff, + action_length: 16, + subtype: 34 + uint8 :_clause + uint8 :n_clauses + uint32 :conjunction_id + + def initialize(options) + super(_clause: options[:clause] - 1, + n_clauses: options[:n_clauses], + conjunction_id: options[:conjunction_id]) + end + + def clause + _clause + 1 + end + end + end + NiciraConjunction = OpenFlow13::NiciraConjunction +end diff --git a/lib/pio/open_flow13/nicira_reg_load.rb b/lib/pio/open_flow13/nicira_reg_load.rb index e8d2f438..ba286353 100644 --- a/lib/pio/open_flow13/nicira_reg_load.rb +++ b/lib/pio/open_flow13/nicira_reg_load.rb @@ -1,43 +1,63 @@ -require 'active_support/core_ext/string/inflections' -require 'pio/open_flow/action' +require 'pio/open_flow/nicira_action' +require 'pio/open_flow13/match' module Pio module OpenFlow13 # NXAST_REG_LOAD action - class NiciraRegLoad < OpenFlow::Action - action_header action_type: 0xffff, action_length: 24 - uint32 :experimenter_id, value: 0x2320 - uint16 :experimenter_type, value: 7 - bit10 :offset_internal, initial_value: 0 - bit6 :n_bits_internal - uint32 :destination_internal - uint64 :value_internal - - attr_reader :destination + class NiciraRegLoad < OpenFlow::NiciraAction + nicira_action_header action_type: 0xffff, + action_length: 24, + subtype: 7 + bit10 :_offset, initial_value: 0 + bit6 :_n_bits + struct :_destination do + uint16 :oxm_class + bit7 :oxm_field + bit1 :oxm_hasmask, value: 0 + bit8 :oxm_length + end + uint64 :_value - # rubocop:disable AbcSize - # rubocop:disable LineLength def initialize(value, destination, options = {}) @destination = destination - oxm_klass = Match.const_get(destination.to_s.split('_').map(&:capitalize).join) - super(value_internal: value, - offset_internal: options[:offset] || 0, - n_bits_internal: options[:n_bits] ? options[:n_bits] - 1 : oxm_klass.new.length * 8 - 1, - destination_internal: ((oxm_klass.superclass.const_get(:OXM_CLASS) << 16) | (oxm_klass.const_get(:OXM_FIELD) << 9) | oxm_klass.new.length)) + super(_value: value, + _offset: options[:offset] || 0, + _n_bits: (options[:n_bits] || oxm_length * 8) - 1, + _destination: { oxm_class: oxm_class, + oxm_field: oxm_field, + oxm_length: oxm_length }) end - # rubocop:enable AbcSize - # rubocop:enable LineLength + + attr_reader :destination def offset - offset_internal + _offset end def n_bits - n_bits_internal + 1 + _n_bits + 1 end def value - value_internal + _value + end + + private + + def oxm_class + destination_oxm_class.const_get(:OXM_CLASS) + end + + def oxm_field + destination_oxm_class.const_get(:OXM_FIELD) + end + + def oxm_length + destination_oxm_class.new.length + end + + def destination_oxm_class + Match.const_get(@destination.to_s.split('_').map(&:capitalize).join) end end end diff --git a/lib/pio/open_flow13/nicira_reg_move.rb b/lib/pio/open_flow13/nicira_reg_move.rb index 524406b9..072efca0 100644 --- a/lib/pio/open_flow13/nicira_reg_move.rb +++ b/lib/pio/open_flow13/nicira_reg_move.rb @@ -1,42 +1,84 @@ -require 'active_support/core_ext/string/inflections' -require 'pio/open_flow/action' +require 'pio/open_flow/nicira_action' +require 'pio/open_flow13/match' module Pio module OpenFlow13 # NXAST_REG_MOVE action - class NiciraRegMove < OpenFlow::Action - action_header action_type: 0xffff, action_length: 24 - uint32 :experimenter_id, value: 0x2320 - uint16 :experimenter_type, value: 6 - uint16 :n_bits, initial_value: -> { source_oxm_length * 8 } - uint16 :source_offset, value: 0 - uint16 :destination_offset, value: 0 - uint16 :source_oxm_class - bit7 :source_oxm_field - bit1 :source_oxm_hasmask, value: 0 - uint8 :source_oxm_length - uint16 :destination_oxm_class - bit7 :destination_oxm_field - bit1 :destination_oxm_hasmask, value: 0 - uint8 :destination_oxm_length - - attr_reader :from - attr_reader :to - - # rubocop:disable AbcSize - def initialize(options) - @from = options.fetch(:from) - @to = options.fetch(:to) - from_klass = Match.const_get(@from.to_s.classify) - to_klass = Match.const_get(@to.to_s.classify) - super(source_oxm_class: from_klass.superclass.const_get(:OXM_CLASS), - source_oxm_field: from_klass.const_get(:OXM_FIELD), - source_oxm_length: from_klass.new.length, - destination_oxm_class: to_klass.superclass.const_get(:OXM_CLASS), - destination_oxm_field: to_klass.const_get(:OXM_FIELD), - destination_oxm_length: to_klass.new.length) - end - # rubocop:enable AbcSize + class NiciraRegMove < OpenFlow::NiciraAction + nicira_action_header action_type: 0xffff, + action_length: 24, + subtype: 6 + uint16 :n_bits, initial_value: -> { _source[:oxm_length] * 8 } + uint16 :source_offset, initial_value: 0 + uint16 :destination_offset, initial_value: 0 + struct :_source do + uint16 :oxm_class + bit7 :oxm_field + bit1 :oxm_hasmask, value: 0 + uint8 :oxm_length + end + struct :_destination do + uint16 :oxm_class + bit7 :oxm_field + bit1 :oxm_hasmask, value: 0 + uint8 :oxm_length + end + + # rubocop:disable MethodLength + def initialize(arguments) + @source = arguments.fetch(:source) + @destination = arguments.fetch(:destination) + registers = { _source: { oxm_class: source_oxm_class, + oxm_field: source_oxm_field, + oxm_length: source_oxm_length }, + _destination: { oxm_class: destination_oxm_class, + oxm_field: destination_oxm_field, + oxm_length: destination_oxm_length } } + options = [:n_bits, + :source_offset, + :destination_offset].each_with_object({}) do |each, opts| + opts[each] = arguments[each] if arguments[each] + end + super registers.merge(options) + end + # rubocop:enable MethodLength + + attr_reader :source + attr_reader :destination + + private + + def source_oxm_class + source_class.const_get(:OXM_CLASS) + end + + def source_oxm_field + source_class.const_get(:OXM_FIELD) + end + + def source_oxm_length + source_class.new.length + end + + def source_class + Match.const_get(@source.to_s.split('_').map(&:capitalize).join) + end + + def destination_oxm_class + destination_class.const_get(:OXM_CLASS) + end + + def destination_oxm_field + destination_class.const_get(:OXM_FIELD) + end + + def destination_oxm_length + destination_class.new.length + end + + def destination_class + Match.const_get(@destination.to_s.split('_').map(&:capitalize).join) + end end end end diff --git a/lib/pio/open_flow13/nicira_send_out_port.rb b/lib/pio/open_flow13/nicira_send_out_port.rb index dfdbdbb7..ff250ffa 100644 --- a/lib/pio/open_flow13/nicira_send_out_port.rb +++ b/lib/pio/open_flow13/nicira_send_out_port.rb @@ -1,37 +1,53 @@ -require 'pio/open_flow/action' +require 'pio/open_flow/nicira_action' +require 'pio/open_flow13/match' +require 'pio/open_flow13/send_out_port' module Pio module OpenFlow13 # NXAST_OUTPUT_REG action - class NiciraSendOutPort < OpenFlow::Action - action_header action_type: 0xffff, action_length: 24 - uint32 :experimenter_id, value: 0x2320 - uint16 :experimenter_type, value: 15 - bit10 :offset_internal, value: 0 - bit6 :n_bits_internal - uint32 :source_internal - uint16 :max_length, value: 0 + class NiciraSendOutPort < OpenFlow::NiciraAction + nicira_action_header action_type: 0xffff, + action_length: 24, + subtype: 15 + bit10 :_offset + bit6 :_n_bits + struct :_source do + uint16 :oxm_class + bit7 :oxm_field + bit1 :oxm_hasmask, value: 0 + bit8 :oxm_length + end + uint16 :max_length string :zero, length: 6 - attr_reader :source - - # rubocop:disable AbcSize - # rubocop:disable LineLength - def initialize(source) + def initialize(source, options = {}) @source = source - oxm_klass = Match.const_get(source.to_s.split('_').map(&:capitalize).join) - super(n_bits_internal: oxm_klass.new.length * 8 - 1, - source_internal: ((oxm_klass.superclass.const_get(:OXM_CLASS) << 16) | (oxm_klass.const_get(:OXM_FIELD) << 9) | oxm_klass.new.length)) + super(_n_bits: (options[:n_bits] || oxm_length * 8) - 1, + _offset: options[:offset] || 0, + _source: { oxm_class: source_oxm_class.const_get(:OXM_CLASS), + oxm_field: source_oxm_class.const_get(:OXM_FIELD), + oxm_length: oxm_length }, + max_length: options[:max_length] || SendOutPort::NO_BUFFER) end - # rubocop:enable AbcSize - # rubocop:enable LineLength + + attr_reader :source def offset - offset_internal + _offset end def n_bits - n_bits_internal + 1 + _n_bits + 1 + end + + private + + def oxm_length + source_oxm_class.new.length + end + + def source_oxm_class + Match.const_get(@source.to_s.split('_').map(&:capitalize).join) end end end diff --git a/lib/pio/open_flow13/nicira_stack_pop.rb b/lib/pio/open_flow13/nicira_stack_pop.rb new file mode 100644 index 00000000..a60f7421 --- /dev/null +++ b/lib/pio/open_flow13/nicira_stack_pop.rb @@ -0,0 +1,49 @@ +require 'pio/open_flow/nicira_action' +require 'pio/open_flow13/match' + +module Pio + module OpenFlow13 + # NXAST_STACK_POP action + class NiciraStackPop < OpenFlow::NiciraAction + nicira_action_header action_type: 0xffff, + action_length: 24, + subtype: 28 + uint16 :_offset + struct :field do + uint16 :oxm_class + bit7 :oxm_field + bit1 :oxm_hasmask, value: 0 + uint8 :oxm_length + end + uint16 :_n_bits + string :padding, length: 6 + hide :padding + + def initialize(field, options = {}) + @field = field + super(_offset: options[:offset] || 0, + _n_bits: (options[:n_bits] || oxm_length * 8) + 1, + field: { oxm_class: field_oxm_class.const_get(:OXM_CLASS), + oxm_field: field_oxm_class.const_get(:OXM_FIELD), + oxm_length: oxm_length }) + end + + attr_reader :field + alias offset _offset + + def n_bits + _n_bits - 1 + end + + private + + def oxm_length + field_oxm_class.new.length + end + + def field_oxm_class + Match.const_get(@field.to_s.split('_').map(&:capitalize).join) + end + end + end +end diff --git a/lib/pio/open_flow13/nicira_stack_push.rb b/lib/pio/open_flow13/nicira_stack_push.rb new file mode 100644 index 00000000..59e86670 --- /dev/null +++ b/lib/pio/open_flow13/nicira_stack_push.rb @@ -0,0 +1,49 @@ +require 'pio/open_flow/nicira_action' +require 'pio/open_flow13/match' + +module Pio + module OpenFlow13 + # NXAST_STACK_PUSH action + class NiciraStackPush < OpenFlow::NiciraAction + nicira_action_header action_type: 0xffff, + action_length: 24, + subtype: 27 + uint16 :_offset + struct :field do + uint16 :oxm_class + bit7 :oxm_field + bit1 :oxm_hasmask, value: 0 + uint8 :oxm_length + end + uint16 :_n_bits + string :padding, length: 6 + hide :padding + + def initialize(field, options = {}) + @field = field + super(_offset: options[:offset] || 0, + _n_bits: (options[:n_bits] || oxm_length * 8) + 1, + field: { oxm_class: field_oxm_class.const_get(:OXM_CLASS), + oxm_field: field_oxm_class.const_get(:OXM_FIELD), + oxm_length: oxm_length }) + end + + attr_reader :field + alias offset _offset + + def n_bits + _n_bits - 1 + end + + private + + def oxm_length + field_oxm_class.new.length + end + + def field_oxm_class + Match.const_get(@field.to_s.split('_').map(&:capitalize).join) + end + end + end +end diff --git a/lib/pio/open_flow13/packet_in.rb b/lib/pio/open_flow13/packet_in.rb index a119d2b2..5f1bda8b 100644 --- a/lib/pio/open_flow13/packet_in.rb +++ b/lib/pio/open_flow13/packet_in.rb @@ -1,3 +1,4 @@ +require 'active_support/core_ext/object/try' require 'pio/open_flow' require 'pio/open_flow13/match' require 'pio/parser' @@ -8,8 +9,9 @@ module OpenFlow13 class PacketIn < OpenFlow::Message # Why is this packet being sent to the controller? # (enum ofp_packet_in_reason) + # rubocop:disable LineLength class Reason < BinData::Primitive - REASONS = { no_match: 0, action: 1, invalid_ttl: 2 } + REASONS = { no_match: 0, action: 1, invalid_ttl: 2 }.freeze uint8 :reason @@ -22,9 +24,8 @@ def set(value) end end - open_flow_header(version: 4, - message_type: 10, - message_length: lambda do + open_flow_header(version: 4, type: 10, + length: lambda do 24 + match.length + padding.length + raw_data.length end) uint32 :buffer_id @@ -35,11 +36,11 @@ def set(value) oxm :match string :padding, length: 2 hide :padding - string :raw_data, read_length: :total_len + string :raw_data, read_length: -> { length - header_length - (16 + match.length + 2) } attr_accessor :datapath_id - alias_method :dpid, :datapath_id - alias_method :dpid=, :datapath_id= + alias dpid datapath_id + alias dpid= datapath_id= def data @data ||= Pio::Parser.read(raw_data) @@ -50,8 +51,10 @@ def in_port end def method_missing(method, *args) - data.__send__(method, *args).snapshot + bindata_value = data.__send__(method, *args) + bindata_value.try(:snapshot) || bindata_value end + # rubocop:enable LineLength end end end diff --git a/lib/pio/open_flow13/packet_out.rb b/lib/pio/open_flow13/packet_out.rb index bb2eb309..8afa6b20 100644 --- a/lib/pio/open_flow13/packet_out.rb +++ b/lib/pio/open_flow13/packet_out.rb @@ -1,6 +1,7 @@ +require 'active_support/core_ext/object/try' require 'pio/open_flow' +require 'pio/open_flow/buffer_id' require 'pio/open_flow13/actions' -require 'pio/open_flow13/buffer_id' module Pio module OpenFlow13 @@ -14,7 +15,7 @@ class InPort < BinData::Primitive uint32 :in_port def get - (in_port == CONTROLLER) ? :controller : in_port + in_port == CONTROLLER ? :controller : in_port end def set(value) @@ -22,24 +23,24 @@ def set(value) end end - open_flow_header(version: 4, - message_type: 13, - message_length: lambda do + open_flow_header(version: 4, type: 13, + length: lambda do 24 + actions_length + raw_data.length end) buffer_id :buffer_id in_port :in_port uint16 :actions_length, initial_value: -> { actions.binary.length } string :padding, length: 6 - actions :actions, length: :actions_length - string :raw_data, read_length: -> { message_length - 24 - actions_length } + actions13 :actions, length: :actions_length + string :raw_data, read_length: -> { length - 24 - actions_length } def data @data ||= Pio::Parser.read(raw_data) end def method_missing(method, *args) - data.__send__(method, *args).snapshot + bindata_value = data.__send__(method, *args) + bindata_value.try(:snapshot) || bindata_value end end end diff --git a/lib/pio/open_flow13/set_ip_ttl.rb b/lib/pio/open_flow13/set_ip_ttl.rb new file mode 100644 index 00000000..bdfeab13 --- /dev/null +++ b/lib/pio/open_flow13/set_ip_ttl.rb @@ -0,0 +1,16 @@ +require 'pio/open_flow/action' + +module Pio + module OpenFlow13 + # Sets IP TTL + class SetIpTtl < OpenFlow::Action + action_header action_type: 23, action_length: 8 + uint8 :ttl + string :padding, length: 3 + + def initialize(ttl) + super(ttl: ttl) + end + end + end +end diff --git a/lib/pio/open_flow13/stats_request.rb b/lib/pio/open_flow13/stats_request.rb index 6980c046..d46a831a 100644 --- a/lib/pio/open_flow13/stats_request.rb +++ b/lib/pio/open_flow13/stats_request.rb @@ -12,7 +12,7 @@ class StatsType < BinData::Primitive :table, :port, :queue, - :vendor] + :vendor].freeze endian :big @@ -27,9 +27,7 @@ def get end end - open_flow_header version: 4, - message_type: 16, - message_length: 12 + open_flow_header version: 4, type: 16, length: 12 stats_type :stats_type uint16 :stats_flags end diff --git a/lib/pio/open_flow13/write_metadata.rb b/lib/pio/open_flow13/write_metadata.rb index 180f3081..edd70d76 100644 --- a/lib/pio/open_flow13/write_metadata.rb +++ b/lib/pio/open_flow13/write_metadata.rb @@ -1,52 +1,54 @@ require 'forwardable' +require 'pio/open_flow/action' -# Base module. module Pio - # Write metadata - class WriteMetadata - # OpenFlow 1.3.4 OFPIT_WRITE_METADATA instruction format - class Format < BinData::Record - endian :big - - uint16 :instruction_type, value: 2 - uint16 :instruction_length, value: 24 - uint32 :padding - uint64 :metadata - uint64 :metadata_mask - end + module OpenFlow13 + # Write metadata + class WriteMetadata < OpenFlow::Action + # OpenFlow 1.3.4 OFPIT_WRITE_METADATA instruction format + class Format < BinData::Record + endian :big + + uint16 :instruction_type, value: 2 + uint16 :instruction_length, value: 24 + uint32 :padding + uint64 :metadata + uint64 :metadata_mask + end - def self.read(raw_data) - allocate.tap do |write_metadata| - write_metadata.instance_variable_set :@format, Format.read(raw_data) + def self.read(raw_data) + allocate.tap do |write_metadata| + write_metadata.instance_variable_set :@format, Format.read(raw_data) + end end - end - extend Forwardable + extend Forwardable - def_delegators :@format, :instruction_type - def_delegators :@format, :instruction_length - def_delegators :@format, :metadata - def_delegators :@format, :metadata_mask - def_delegators :@format, :to_binary_s + def_delegators :@format, :instruction_type + def_delegators :@format, :instruction_length + def_delegators :@format, :metadata + def_delegators :@format, :metadata_mask + def_delegators :@format, :to_binary_s - def initialize(user_options) - @options = user_options - @format = Format.new(options) - end + def initialize(user_options) + @options = user_options + @format = Format.new(options) + end - def options - { - metadata: metadata_option, - metadata_mask: metadata_mask_option - } - end + def options + { + metadata: metadata_option, + metadata_mask: metadata_mask_option + } + end - def metadata_option - @options[:metadata] - end + def metadata_option + @options[:metadata] + end - def metadata_mask_option - @options[:metadata_mask] + def metadata_mask_option + @options[:metadata_mask] + end end end end diff --git a/lib/pio/options.rb b/lib/pio/options.rb index 3765bb92..006ff292 100644 --- a/lib/pio/options.rb +++ b/lib/pio/options.rb @@ -14,6 +14,11 @@ def self.option(name) const_get(:OPTIONS) << name end + def initialize(options) + validate options + @options = options + end + private def validate(user_options) @@ -42,7 +47,7 @@ def options def check_unknown(user_options) valid_options = mandatory_options + options user_options.keys.each do |each| - fail "Unknown option: #{each}." unless valid_options.include?(each) + raise "Unknown option: #{each}." unless valid_options.include?(each) end end @@ -54,10 +59,10 @@ def check_mandatory(user_options) def check_existence(user_options, key) value = user_options.fetch(key) do |missing_key| - fail ArgumentError, "The #{missing_key} option should be passed." + raise ArgumentError, "The #{missing_key} option should be passed." end return if value - fail(ArgumentError, "The #{key} option shouldn't be #{value.inspect}.") + raise(ArgumentError, "The #{key} option shouldn't be #{value.inspect}.") end end end diff --git a/lib/pio/parser.rb b/lib/pio/parser.rb index fe16e0e3..af65886a 100644 --- a/lib/pio/parser.rb +++ b/lib/pio/parser.rb @@ -1,27 +1,18 @@ +require 'pio/ethernet_frame' require 'pio/ethernet_header' require 'pio/ipv4_header' module Pio # Raw data parser. class Parser - # Ethernet header parser - class EthernetFrame < BinData::Record - endian :big - - mac_address :destination_mac - mac_address :source_mac - uint16 :ether_type - rest :rest - end - # IPv4 packet parser class IPv4Packet < BinData::Record - include EthernetHeader - include IPv4Header + include Ethernet + include IPv4 endian :big - ethernet_header ether_type: EtherType::IPV4 + ethernet_header ether_type: Ethernet::Type::IPV4 ipv4_header uint16 :transport_source_port @@ -33,11 +24,11 @@ class IPv4Packet < BinData::Record def self.read(raw_data) ethernet_frame = EthernetFrame.read(raw_data) case ethernet_frame.ether_type - when EthernetHeader::EtherType::IPV4, EthernetHeader::EtherType::VLAN + when Ethernet::Type::IPV4, Ethernet::Type::VLAN IPv4Packet.read raw_data - when EthernetHeader::EtherType::ARP + when Ethernet::Type::ARP Pio::Arp.read raw_data - when EthernetHeader::EtherType::LLDP + when Ethernet::Type::LLDP Pio::Lldp.read raw_data else ethernet_frame diff --git a/lib/pio/ruby_dumper.rb b/lib/pio/ruby_dumper.rb new file mode 100644 index 00000000..5e34041e --- /dev/null +++ b/lib/pio/ruby_dumper.rb @@ -0,0 +1,69 @@ +module Pio + # defines to_ruby method + module RubyDumper + # Returns a Ruby code representation of this packet, such that + # it can be eval'ed and sent later. + # + # rubocop:disable AbcSize + # rubocop:disable MethodLength + # rubocop:disable CyclomaticComplexity + # rubocop:disable PerceivedComplexity + def to_ruby + pack_template = '' + bytes = '' + bit = false + bit_names = [] + total_bit_length = 0 + field_names.each do |each| + next unless __send__("#{each}?") + if /Bit(\d+)$/ =~ __send__(each).class.to_s + bit_length = Regexp.last_match(1).to_i + total_bit_length += bit_length + if bit + bit_names << each + bytes << format("_%0#{bit_length}b", __send__(each)) + else + bit_names = [each] + bytes << format(" 0b%0#{bit_length}b", __send__(each)) + end + bit = true + else + if bit + bytes << ", # #{bit_names.join(', ')}\n" + if total_bit_length == 8 + pack_template << 'C' + elsif total_bit_length == 16 + pack_template << 'n' + else + raise + end + total_bit_length = 0 + bit_names = [] + bit = false + end + list = (@format || self).__send__(each).to_bytes + next if list.empty? + bytes << " #{list}, # #{each}\n" + pack_template << 'C' * (list.count(',') + 1) + end + end.compact + + template = '' + until pack_template.empty? + if /^(.)(\1+)/ =~ pack_template + pack_template.sub!(/^(.)(\1+)/, '') + template << + "#{Regexp.last_match(1)}#{Regexp.last_match(2).length + 1}" + else + pack_template.sub!(/^(.)/, '') + template << Regexp.last_match(1) + end + end + "[\n#{bytes}].pack('#{template}')" + end + # rubocop:enable AbcSize + # rubocop:enable MethodLength + # rubocop:enable CyclomaticComplexity + # rubocop:enable PerceivedComplexity + end +end diff --git a/lib/pio/type/ether_type.rb b/lib/pio/type/ether_type.rb new file mode 100644 index 00000000..6a7287f1 --- /dev/null +++ b/lib/pio/type/ether_type.rb @@ -0,0 +1,31 @@ +require 'bindata' + +module Pio + module Type + # Ether type + class EtherType < BinData::Primitive + endian :big + + uint16 :ether_type + + def set(value) + self.ether_type = value + end + + def get + ether_type + end + + # This method smells of :reek:UncommunicativeVariableName + def to_bytes + byte1 = format('%02x', (self & 0xff00) >> 8) + byte2 = format('%02x', self & 0xff) + "0x#{byte1}, 0x#{byte2}" + end + + def inspect + Kernel.format '0x%04x', self + end + end + end +end diff --git a/lib/pio/type/ip_address.rb b/lib/pio/type/ip_address.rb index 1972195c..1703b169 100644 --- a/lib/pio/type/ip_address.rb +++ b/lib/pio/type/ip_address.rb @@ -26,6 +26,14 @@ def &(other) def ==(other) get == other end + + def to_bytes + octets.map(&:to_hex).join(', ') + end + + def inspect + %("#{get}") + end end end end diff --git a/lib/pio/type/mac_address.rb b/lib/pio/type/mac_address.rb index d11b478a..12000f95 100644 --- a/lib/pio/type/mac_address.rb +++ b/lib/pio/type/mac_address.rb @@ -1,5 +1,7 @@ require 'bindata' require 'pio/mac' +require 'pio/monkey_patch/integer' +require 'pio/monkey_patch/uint' module Pio module Type @@ -16,6 +18,14 @@ def get str + format('%02x', each) end.hex) end + + def to_bytes + octets.map(&:to_hex).join(', ') + end + + def inspect + %("#{get}") + end end end end diff --git a/lib/pio/udp.rb b/lib/pio/udp.rb index 30cc0dd6..6b3cf335 100644 --- a/lib/pio/udp.rb +++ b/lib/pio/udp.rb @@ -6,12 +6,12 @@ module Pio # UDP packet format class Udp < BinData::Record - include EthernetHeader - include IPv4Header + include Ethernet + include IPv4 include UdpHeader endian :big - ethernet_header ether_type: EtherType::IPV4 + ethernet_header ether_type: Ethernet::Type::IPV4 ipv4_header ip_protocol: ProtocolNumber::UDP udp_header rest :udp_payload diff --git a/lib/pio/udp_header.rb b/lib/pio/udp_header.rb index 71fb4b2c..d32f0786 100644 --- a/lib/pio/udp_header.rb +++ b/lib/pio/udp_header.rb @@ -11,7 +11,7 @@ class PseudoUdpHeader < BinData::Record ip_address :source_ip_address ip_address :destination_ip_address uint8 :padding - uint8 :ip_protocol, value: IPv4Header::ProtocolNumber::UDP + uint8 :ip_protocol, value: IPv4::ProtocolNumber::UDP uint16 :udp_length end diff --git a/lib/pio/version.rb b/lib/pio/version.rb index 2759bffb..5234bb3b 100644 --- a/lib/pio/version.rb +++ b/lib/pio/version.rb @@ -1,5 +1,5 @@ # Base module. module Pio # gem version. - VERSION = '0.30.0'.freeze + VERSION = '0.30.1'.freeze end diff --git a/pio.gemspec b/pio.gemspec index 5d7ecfb5..906e5fdc 100644 --- a/pio.gemspec +++ b/pio.gemspec @@ -8,57 +8,24 @@ Gem::Specification.new do |gem| gem.summary = 'Packet parser and generator.' gem.description = 'Pure ruby packet parser and generator.' - gem.license = 'GPL3' + gem.licenses = %w(GPLv2 MIT) gem.authors = ['Yasuhito Takamiya'] gem.email = ['yasuhito@gmail.com'] gem.homepage = 'http://github.com/trema/pio' - gem.files = %w(CONTRIBUTING.md LICENSE Rakefile pio.gemspec) + gem.files = %w(CONTRIBUTING.md Rakefile pio.gemspec) gem.files += Dir.glob('lib/**/*.rb') - gem.files += Dir.glob('bin/**/*') gem.files += Dir.glob('spec/**/*') - gem.files += Dir.glob('examples/**/*') gem.require_paths = ['lib'] - gem.extra_rdoc_files = %w(README.md CHANGELOG.md LICENSE CONTRIBUTING.md) + gem.extra_rdoc_files = %w(README.md CHANGELOG.md CONTRIBUTING.md) gem.test_files = Dir.glob('spec/**/*') gem.test_files += Dir.glob('features/**/*') - gem.required_ruby_version = '>= 2.0.0' + gem.required_ruby_version = '>= 2.2.2' gem.add_dependency 'bindata', '~> 2.1.0' - gem.add_dependency 'activesupport', '~> 4.2', '>= 4.2.4' - - gem.add_development_dependency 'bundler', '~> 1.10.6' - gem.add_development_dependency 'pry', '~> 0.10.3' - gem.add_development_dependency 'rake' - - # Guard - gem.add_development_dependency 'guard', '~> 2.13.0' - gem.add_development_dependency 'guard-bundler', '~> 2.1.0' - gem.add_development_dependency 'guard-cucumber', '~> 1.6.0' - gem.add_development_dependency 'guard-rspec', '~> 4.6.4' - gem.add_development_dependency 'guard-rubocop', '~> 1.2.0' - gem.add_development_dependency 'rb-fchange', '~> 0.0.6' - gem.add_development_dependency 'rb-fsevent', '~> 0.9.6' - gem.add_development_dependency 'rb-inotify', '~> 0.9.5' - gem.add_development_dependency 'terminal-notifier-guard', '~> 1.6.4' - - # Docs - gem.add_development_dependency 'inch', '~> 0.7.0' - gem.add_development_dependency 'relish', '~> 0.7.1' - gem.add_development_dependency 'yard', '~> 0.8.7.6' - - # Test - gem.add_development_dependency 'codeclimate-test-reporter', '~> 0.4.8' - gem.add_development_dependency 'coveralls', '~> 0.8.3' - gem.add_development_dependency 'cucumber', '~> 2.1.0' - gem.add_development_dependency 'flay', '~> 2.6.1' - gem.add_development_dependency 'flog', '~> 4.3.2' - gem.add_development_dependency 'reek', '~> 3.6.0' - gem.add_development_dependency 'rspec', '~> 3.3.0' - gem.add_development_dependency 'rspec-given', '~> 3.7.1' - gem.add_development_dependency 'rubocop', '~> 0.34.2' + gem.add_dependency 'activesupport', '~> 5.0.2' end diff --git a/spec/pio/arp/reply/options_spec.rb b/spec/pio/arp/reply/options_spec.rb deleted file mode 100644 index 8069c0ba..00000000 --- a/spec/pio/arp/reply/options_spec.rb +++ /dev/null @@ -1,143 +0,0 @@ -require 'pio' - -describe Pio::Arp::Reply::Options, '.new' do - Given(:mandatory_options) do - { - source_mac: 0x00169d1d9cc4, - destination_mac: 0x002682ebead1, - sender_protocol_address: 0xc0a85303, - target_protocol_address: 0xc0a853fe - } - end - - context 'with all mandatory options' do - Given(:options) do - Pio::Arp::Reply::Options.new(mandatory_options) - end - - describe '#to_hash' do - When(:result) { options.to_hash } - - Then { result[:source_mac] == Pio::Mac.new(0x00169d1d9cc4) } - Then { result[:destination_mac] == Pio::Mac.new(0x002682ebead1) } - Then do - result[:sender_protocol_address] == Pio::IPv4Address.new(0xc0a85303) - end - Then do - result[:target_protocol_address] == Pio::IPv4Address.new(0xc0a853fe) - end - end - end - - context 'when source_mac is not passed' do - Given(:user_options) do - mandatory_options.delete(:source_mac) - mandatory_options - end - - When(:result) { Pio::Arp::Reply::Options.new(user_options) } - - Then do - result == - Failure(ArgumentError, 'The source_mac option should be passed.') - end - end - - context 'with source_mac = nil' do - Given(:user_options) do - mandatory_options.update(source_mac: nil) - mandatory_options - end - - When(:result) { Pio::Arp::Reply::Options.new(user_options) } - - Then do - result == - Failure(ArgumentError, "The source_mac option shouldn't be nil.") - end - end - - context 'when destination_mac is not passed' do - Given(:user_options) do - mandatory_options.delete(:destination_mac) - mandatory_options - end - - When(:result) { Pio::Arp::Reply::Options.new(user_options) } - - Then do - result == - Failure(ArgumentError, 'The destination_mac option should be passed.') - end - end - - context 'with destination_mac = nil' do - Given(:user_options) do - mandatory_options.update(destination_mac: nil) - mandatory_options - end - - When(:result) { Pio::Arp::Reply::Options.new(user_options) } - - Then do - result == - Failure(ArgumentError, "The destination_mac option shouldn't be nil.") - end - end - - context 'when sender_protocol_address is not passed' do - Given(:user_options) do - mandatory_options.delete(:sender_protocol_address) - mandatory_options - end - - When(:result) { Pio::Arp::Reply::Options.new(user_options) } - - Then do - result == Failure(ArgumentError, - 'The sender_protocol_address option should be passed.') - end - end - - context 'with sender_protocol_address = nil' do - Given(:user_options) do - mandatory_options.update(sender_protocol_address: nil) - mandatory_options - end - - When(:result) { Pio::Arp::Reply::Options.new(user_options) } - - Then do - result == Failure(ArgumentError, - "The sender_protocol_address option shouldn't be nil.") - end - end - - context 'when target_protocol_address is not passed' do - Given(:user_options) do - mandatory_options.delete(:target_protocol_address) - mandatory_options - end - - When(:result) { Pio::Arp::Reply::Options.new(user_options) } - - Then do - result == Failure(ArgumentError, - 'The target_protocol_address option should be passed.') - end - end - - context 'with target_protocol_address = nil' do - Given(:user_options) do - mandatory_options.update(target_protocol_address: nil) - mandatory_options - end - - When(:result) { Pio::Arp::Reply::Options.new(user_options) } - - Then do - result == Failure(ArgumentError, - "The target_protocol_address option shouldn't be nil.") - end - end -end diff --git a/spec/pio/arp/reply_spec.rb b/spec/pio/arp/reply_spec.rb index ec4cd453..33fd8fc1 100644 --- a/spec/pio/arp/reply_spec.rb +++ b/spec/pio/arp/reply_spec.rb @@ -1,9 +1,5 @@ require 'pio' -describe Pio::ARP::Reply do - Then { Pio::ARP::Reply == Pio::Arp::Reply } -end - describe Pio::Arp::Reply, '.new' do ARP_REPLY_DUMP = [ diff --git a/spec/pio/arp/request/options_spec.rb b/spec/pio/arp/request/options_spec.rb deleted file mode 100644 index 40bea2ba..00000000 --- a/spec/pio/arp/request/options_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -require 'pio' - -describe Pio::Arp::Request::Options, '.new' do - Given(:mandatory_options) do - { - source_mac: 0x002682ebead1, - sender_protocol_address: 0xc0a85303, - target_protocol_address: 0xc0a853fe - } - end - - context 'with all mandatory options' do - Given(:options) do - Pio::Arp::Request::Options.new(mandatory_options) - end - - describe '#to_hash' do - When(:result) { options.to_hash } - - Then { result[:source_mac] == Pio::Mac.new(0x002682ebead1) } - Then do - result[:sender_protocol_address] == Pio::IPv4Address.new(0xc0a85303) - end - Then do - result[:target_protocol_address] == Pio::IPv4Address.new(0xc0a853fe) - end - end - end - - context 'when source_mac is not passed' do - Given(:user_options) do - mandatory_options.delete(:source_mac) - mandatory_options - end - - When(:result) { Pio::Arp::Request::Options.new(user_options) } - - Then do - result == - Failure(ArgumentError, 'The source_mac option should be passed.') - end - end - - context 'with source_mac = nil' do - Given(:user_options) do - mandatory_options.update(source_mac: nil) - mandatory_options - end - - When(:result) { Pio::Arp::Request::Options.new(user_options) } - - Then do - result == - Failure(ArgumentError, "The source_mac option shouldn't be nil.") - end - end - - context 'when sender_protocol_address is not passed' do - Given(:user_options) do - mandatory_options.delete(:sender_protocol_address) - mandatory_options - end - - When(:result) { Pio::Arp::Request::Options.new(user_options) } - - Then do - result == Failure(ArgumentError, - 'The sender_protocol_address option should be passed.') - end - end - - context 'with sender_protocol_address = nil' do - Given(:user_options) do - mandatory_options.update(sender_protocol_address: nil) - mandatory_options - end - - When(:result) { Pio::Arp::Request::Options.new(user_options) } - - Then do - result == Failure(ArgumentError, - "The sender_protocol_address option shouldn't be nil.") - end - end - - context 'when target_protocol_address is not passed' do - Given(:user_options) do - mandatory_options.delete(:target_protocol_address) - mandatory_options - end - - When(:result) { Pio::Arp::Request::Options.new(user_options) } - - Then do - result == Failure(ArgumentError, - 'The target_protocol_address option should be passed.') - end - end - - context 'with target_protocol_address = nil' do - Given(:user_options) do - mandatory_options.update(target_protocol_address: nil) - mandatory_options - end - - When(:result) { Pio::Arp::Request::Options.new(user_options) } - - Then do - result == Failure(ArgumentError, - "The target_protocol_address option shouldn't be nil.") - end - end -end diff --git a/spec/pio/arp/request_spec.rb b/spec/pio/arp/request_spec.rb index bfb8fa10..dd2926e2 100644 --- a/spec/pio/arp/request_spec.rb +++ b/spec/pio/arp/request_spec.rb @@ -1,9 +1,5 @@ require 'pio' -describe Pio::ARP::Request do - Then { Pio::ARP::Request == Pio::Arp::Request } -end - describe Pio::Arp::Request, '.new' do ARP_REQUEST_DUMP = [ diff --git a/spec/pio/arp_spec.rb b/spec/pio/arp_spec.rb index f980bc46..c786577f 100644 --- a/spec/pio/arp_spec.rb +++ b/spec/pio/arp_spec.rb @@ -1,9 +1,5 @@ require 'pio' -describe Pio::ARP do - Then { Pio::ARP == Pio::Arp } -end - describe Pio::Arp do describe '.read' do context 'with an Arp Request packet' do diff --git a/spec/pio/icmp/reply_spec.rb b/spec/pio/icmp/reply_spec.rb index 10a757c2..bfcc7fff 100644 --- a/spec/pio/icmp/reply_spec.rb +++ b/spec/pio/icmp/reply_spec.rb @@ -1,9 +1,5 @@ require 'pio' -describe Pio::ICMP::Reply do - Then { Pio::ICMP::Reply == Pio::Icmp::Reply } -end - describe Pio::Icmp::Reply, '.new' do context 'with echo_data' do Given(:icmp_reply) do @@ -12,8 +8,8 @@ source_mac: '24:db:ac:41:e5:5b', source_ip_address: '8.8.8.8', destination_ip_address: '192.168.1.102', - identifier: 0x123, - sequence_number: 0x321, + icmp_identifier: 0x123, + icmp_sequence_number: 0x321, echo_data: 'abcdefghijklmnopqrstuvwabcdefghi' ) end @@ -77,8 +73,8 @@ source_mac: '24:db:ac:41:e5:5b', source_ip_address: '8.8.8.8', destination_ip_address: '192.168.1.102', - identifier: 0x123, - sequence_number: 0x321 + icmp_identifier: 0x123, + icmp_sequence_number: 0x321 ) end diff --git a/spec/pio/icmp/request_spec.rb b/spec/pio/icmp/request_spec.rb index 84543706..2efd47c7 100644 --- a/spec/pio/icmp/request_spec.rb +++ b/spec/pio/icmp/request_spec.rb @@ -1,9 +1,5 @@ require 'pio' -describe Pio::ICMP::Request do - Then { Pio::ICMP::Request == Pio::Icmp::Request } -end - describe Pio::Icmp do Given(:icmp_request) do Pio::Icmp::Request.new( @@ -11,8 +7,8 @@ source_mac: '74:e5:0b:2a:18:f8', source_ip_address: '192.168.1.101', destination_ip_address: '8.8.8.8', - identifier: 0x123, - sequence_number: 0x321, + icmp_identifier: 0x123, + icmp_sequence_number: 0x321, echo_data: 'abcdefghijklmnopqrstuvwabcdefghi' ).to_binary end @@ -31,8 +27,8 @@ source_mac: '74:e5:0b:2a:18:f8', source_ip_address: '192.168.1.101', destination_ip_address: '8.8.8.8', - identifier: 0x123, - sequence_number: 0x321, + icmp_identifier: 0x123, + icmp_sequence_number: 0x321, echo_data: 'abcdefghijklmnopqrstuvwabcdefghi' ) end @@ -97,8 +93,8 @@ source_mac: '74:e5:0b:2a:18:f8', source_ip_address: '192.168.1.101', destination_ip_address: '8.8.8.8', - identifier: 0x123, - sequence_number: 0x321 + icmp_identifier: 0x123, + icmp_sequence_number: 0x321 ) end diff --git a/spec/pio/icmp_spec.rb b/spec/pio/icmp_spec.rb index 65b5d2a4..4a88ca6c 100644 --- a/spec/pio/icmp_spec.rb +++ b/spec/pio/icmp_spec.rb @@ -1,9 +1,5 @@ require 'pio' -describe Pio::ICMP do - Then { Pio::ICMP == Pio::Icmp } -end - describe Pio::Icmp, '.read' do context 'with an ICMP request frame' do Given(:icmp_request_dump) do @@ -64,17 +60,17 @@ Then { icmp_request.ether_type == 0x0800 } Then { icmp_request.ip_version == 0x4 } Then { icmp_request.ip_header_length == 0x5 } - Then { icmp_request.ip_type_of_service == 0x0 } + Then { icmp_request.ip_type_of_service.zero? } Then { icmp_request.ip_total_length == 60 } Then { icmp_request.ip_identifier == 0x39d3 } - Then { icmp_request.ip_fragment == 0 } + Then { icmp_request.ip_fragment.zero? } Then { icmp_request.ip_ttl == 128 } Then { icmp_request.ip_protocol == 1 } Then { icmp_request.ip_header_checksum == 0x2ed0 } Then { icmp_request.source_ip_address.to_s == '192.168.1.102' } Then { icmp_request.destination_ip_address.to_s == '8.8.8.8' } Then { icmp_request.icmp_type == 8 } - Then { icmp_request.icmp_code == 0 } + Then { icmp_request.icmp_code.zero? } Then { icmp_request.icmp_checksum == 0x4c5b } Then { icmp_request.icmp_identifier == 0x0100 } Then { icmp_request.icmp_sequence_number == 0x0001 } @@ -137,17 +133,17 @@ Then { icmp_reply.ether_type == 0x0800 } Then { icmp_reply.ip_version == 0x4 } Then { icmp_reply.ip_header_length == 0x5 } - Then { icmp_reply.ip_type_of_service == 0 } + Then { icmp_reply.ip_type_of_service.zero? } Then { icmp_reply.ip_total_length == 60 } - Then { icmp_reply.ip_identifier == 0 } - Then { icmp_reply.ip_fragment == 0 } + Then { icmp_reply.ip_identifier.zero? } + Then { icmp_reply.ip_fragment.zero? } Then { icmp_reply.ip_ttl == 45 } Then { icmp_reply.ip_protocol == 1 } Then { icmp_reply.ip_header_checksum == 0xbba3 } Then { icmp_reply.source_ip_address.to_s == '8.8.8.8' } Then { icmp_reply.destination_ip_address.to_s == '192.168.1.102' } - Then { icmp_reply.icmp_type == 0 } - Then { icmp_reply.icmp_code == 0 } + Then { icmp_reply.icmp_type.zero? } + Then { icmp_reply.icmp_code.zero? } Then { icmp_reply.icmp_checksum == 0x545b } Then { icmp_reply.icmp_identifier == 0x0100 } Then { icmp_reply.icmp_sequence_number == 0x0001 } diff --git a/spec/pio/monkey_patch/integer_spec.rb b/spec/pio/monkey_patch/integer_spec.rb new file mode 100644 index 00000000..58bfcda4 --- /dev/null +++ b/spec/pio/monkey_patch/integer_spec.rb @@ -0,0 +1,23 @@ +require 'pio/monkey_patch/integer' + +describe MonkeyPatch::Integer::BaseConversions do + describe '0#to_hex' do + When(:result) { 0.to_hex } + Then { result == '0x00' } + end + + describe '1#to_hex' do + When(:result) { 1.to_hex } + Then { result == '0x01' } + end + + describe '250#to_hex' do + When(:result) { 250.to_hex } + Then { result == '0xfa' } + end + + describe '4207849484#to_hex' do + When(:result) { 4_207_849_484.to_hex } + Then { result == '0xfaceb00c' } + end +end diff --git a/spec/pio/open_flow/nicira_resubmit_spec.rb b/spec/pio/open_flow/nicira_resubmit_spec.rb new file mode 100644 index 00000000..04101375 --- /dev/null +++ b/spec/pio/open_flow/nicira_resubmit_spec.rb @@ -0,0 +1,19 @@ +require 'pio/open_flow/nicira_resubmit' + +describe Pio::OpenFlow::NiciraResubmit do + describe '.new' do + When(:nicira_resubmit) do + Pio::OpenFlow::NiciraResubmit.new(port_number) + end + + context 'with 1' do + Given(:port_number) { 1 } + + Then { nicira_resubmit.action_type == 0xffff } + Then { nicira_resubmit.action_length == 16 } + Then { nicira_resubmit.vendor == 0x2320 } + Then { nicira_resubmit.subtype == 1 } + Then { nicira_resubmit.in_port == 1 } + end + end +end diff --git a/spec/pio/open_flow/nicira_resubmit_table_spec.rb b/spec/pio/open_flow/nicira_resubmit_table_spec.rb new file mode 100644 index 00000000..225ffdb9 --- /dev/null +++ b/spec/pio/open_flow/nicira_resubmit_table_spec.rb @@ -0,0 +1,20 @@ +require 'pio/open_flow/nicira_resubmit_table' + +describe Pio::OpenFlow::NiciraResubmitTable do + describe '.new' do + When(:nicira_resubmit_table) do + Pio::OpenFlow::NiciraResubmitTable.new(options) + end + + context 'with in_port: 1, table: 1' do + Given(:options) { { in_port: 1, table: 1 } } + + Then { nicira_resubmit_table.action_type == 0xffff } + Then { nicira_resubmit_table.action_length == 16 } + Then { nicira_resubmit_table.vendor == 0x2320 } + Then { nicira_resubmit_table.subtype == 14 } + Then { nicira_resubmit_table.in_port == 1 } + Then { nicira_resubmit_table.table == 1 } + end + end +end diff --git a/spec/pio/open_flow10/error/hello_failed_spec.rb b/spec/pio/open_flow10/error/hello_failed_spec.rb index 318654e5..bf801863 100644 --- a/spec/pio/open_flow10/error/hello_failed_spec.rb +++ b/spec/pio/open_flow10/error/hello_failed_spec.rb @@ -10,7 +10,7 @@ context 'with {}' do Given(:options) { {} } - Then { hello_failed.message_length == 12 } + Then { hello_failed.length == 12 } Then { hello_failed.error_type == :hello_failed } Then { hello_failed.error_code == :incompatible } Then { hello_failed.description == '' } @@ -19,7 +19,7 @@ context "with description: 'error description'" do Given(:options) { { description: 'error description' } } - Then { hello_failed.message_length == 29 } + Then { hello_failed.length == 29 } Then { hello_failed.description == 'error description' } end end diff --git a/spec/pio/open_flow10/flow_mod_spec.rb b/spec/pio/open_flow10/flow_mod_spec.rb index 98a84e6a..0cda05a7 100644 --- a/spec/pio/open_flow10/flow_mod_spec.rb +++ b/spec/pio/open_flow10/flow_mod_spec.rb @@ -30,9 +30,9 @@ end Then { flow_mod.class == Pio::OpenFlow10::FlowMod } - Then { flow_mod.ofp_version == 0x1 } - Then { flow_mod.message_type == 0xe } - Then { flow_mod.message_length == 0x50 } + Then { flow_mod.version == 0x1 } + Then { flow_mod.type == 0xe } + Then { flow_mod.length == 0x50 } Then { flow_mod.transaction_id == 0x15 } Then { flow_mod.xid == 0x15 } @@ -54,19 +54,19 @@ Then { flow_mod.match.in_port == 1 } Then { flow_mod.match.source_mac_address == '00:00:00:00:00:00' } Then { flow_mod.match.destination_mac_address == '00:00:00:00:00:00' } - Then { flow_mod.match.vlan_vid == 0 } - Then { flow_mod.match.vlan_priority == 0 } - Then { flow_mod.match.ether_type == 0 } - Then { flow_mod.match.tos == 0 } - Then { flow_mod.match.ip_protocol == 0 } + Then { flow_mod.match.vlan_vid.zero? } + Then { flow_mod.match.vlan_priority.zero? } + Then { flow_mod.match.ether_type.zero? } + Then { flow_mod.match.tos.zero? } + Then { flow_mod.match.ip_protocol.zero? } Then { flow_mod.match.source_ip_address == '0.0.0.0' } Then { flow_mod.match.destination_ip_address == '0.0.0.0' } - Then { flow_mod.match.transport_source_port == 0 } - Then { flow_mod.match.transport_destination_port == 0 } + Then { flow_mod.match.transport_source_port.zero? } + Then { flow_mod.match.transport_destination_port.zero? } Then { flow_mod.cookie == 1 } Then { flow_mod.command == :add } - Then { flow_mod.idle_timeout == 0 } - Then { flow_mod.hard_timeout == 0 } + Then { flow_mod.idle_timeout.zero? } + Then { flow_mod.hard_timeout.zero? } Then { flow_mod.priority == 0xffff } Then { flow_mod.buffer_id == 0xffffffff } Then { flow_mod.out_port == 2 } @@ -95,9 +95,9 @@ end Then { flow_mod.class == Pio::OpenFlow10::FlowMod } - Then { flow_mod.ofp_version == 0x1 } - Then { flow_mod.message_type == 0xe } - Then { flow_mod.message_length == 0x50 } + Then { flow_mod.version == 0x1 } + Then { flow_mod.type == 0xe } + Then { flow_mod.length == 0x50 } Then { flow_mod.transaction_id == 0x15 } Then { flow_mod.xid == 0x15 } @@ -119,19 +119,19 @@ Then { flow_mod.match.in_port == 1 } Then { flow_mod.match.source_mac_address == '00:00:00:00:00:00' } Then { flow_mod.match.destination_mac_address == '00:00:00:00:00:00' } - Then { flow_mod.match.vlan_vid == 0 } - Then { flow_mod.match.vlan_priority == 0 } - Then { flow_mod.match.ether_type == 0 } - Then { flow_mod.match.tos == 0 } - Then { flow_mod.match.ip_protocol == 0 } + Then { flow_mod.match.vlan_vid.zero? } + Then { flow_mod.match.vlan_priority.zero? } + Then { flow_mod.match.ether_type.zero? } + Then { flow_mod.match.tos.zero? } + Then { flow_mod.match.ip_protocol.zero? } Then { flow_mod.match.source_ip_address == '0.0.0.0' } Then { flow_mod.match.destination_ip_address == '0.0.0.0' } - Then { flow_mod.match.transport_source_port == 0 } - Then { flow_mod.match.transport_destination_port == 0 } + Then { flow_mod.match.transport_source_port.zero? } + Then { flow_mod.match.transport_destination_port.zero? } Then { flow_mod.cookie == 1 } Then { flow_mod.command == :add } - Then { flow_mod.idle_timeout == 0 } - Then { flow_mod.hard_timeout == 0 } + Then { flow_mod.idle_timeout.zero? } + Then { flow_mod.hard_timeout.zero? } Then { flow_mod.priority == 0xffff } Then { flow_mod.buffer_id == 0xffffffff } Then { flow_mod.out_port == 2 } diff --git a/spec/pio/open_flow10/flow_stats_request_spec.rb b/spec/pio/open_flow10/flow_stats_request_spec.rb index ffaa7b01..bd5ade5c 100644 --- a/spec/pio/open_flow10/flow_stats_request_spec.rb +++ b/spec/pio/open_flow10/flow_stats_request_spec.rb @@ -17,9 +17,9 @@ end Then { flow_stats_request.class == Pio::OpenFlow10::FlowStats::Request } - Then { flow_stats_request.ofp_version == 1 } - Then { flow_stats_request.message_type == 16 } - Then { flow_stats_request.message_length == 56 } + Then { flow_stats_request.version == 1 } + Then { flow_stats_request.type == 16 } + Then { flow_stats_request.length == 56 } Then { flow_stats_request.transaction_id == 13 } Then { flow_stats_request.xid == 13 } Then { flow_stats_request.stats_type == :flow } diff --git a/spec/pio/open_flow10/hello_spec.rb b/spec/pio/open_flow10/hello_spec.rb index 21c884a5..bdeadd70 100644 --- a/spec/pio/open_flow10/hello_spec.rb +++ b/spec/pio/open_flow10/hello_spec.rb @@ -2,6 +2,4 @@ describe Pio::OpenFlow10::Hello do it_should_behave_like('an OpenFlow message', Pio::OpenFlow10::Hello) - it_should_behave_like('an OpenFlow message with no body', - Pio::OpenFlow10::Hello) end diff --git a/spec/pio/open_flow10/match_spec.rb b/spec/pio/open_flow10/match_spec.rb index 10854385..0cb980a3 100644 --- a/spec/pio/open_flow10/match_spec.rb +++ b/spec/pio/open_flow10/match_spec.rb @@ -43,15 +43,15 @@ Then { match.in_port == 1 } Then { match.source_mac_address == '00:00:00:00:00:00' } Then { match.destination_mac_address == '00:00:00:00:00:00' } - Then { match.vlan_vid == 0 } - Then { match.vlan_priority == 0 } - Then { match.ether_type == 0 } - Then { match.tos == 0 } - Then { match.ip_protocol == 0 } + Then { match.vlan_vid.zero? } + Then { match.vlan_priority.zero? } + Then { match.ether_type.zero? } + Then { match.tos.zero? } + Then { match.ip_protocol.zero? } Then { match.source_ip_address == '0.0.0.0' } Then { match.destination_ip_address == '0.0.0.0' } - Then { match.transport_source_port == 0 } - Then { match.transport_destination_port == 0 } + Then { match.transport_source_port.zero? } + Then { match.transport_destination_port.zero? } end context 'with a Match binary generated with Pio::OpenFlow10::Match.new' do @@ -78,19 +78,19 @@ ] end And { match.wildcards[:source_ip_address] = 12 } - Then { match.in_port == 0 } + Then { match.in_port.zero? } Then { match.source_mac_address == '00:00:00:00:00:00' } Then { match.destination_mac_address == '00:00:00:00:00:00' } - Then { match.vlan_vid == 0 } - Then { match.vlan_priority == 0 } - Then { match.ether_type == 0 } - Then { match.tos == 0 } - Then { match.ip_protocol == 0 } + Then { match.vlan_vid.zero? } + Then { match.vlan_priority.zero? } + Then { match.ether_type.zero? } + Then { match.tos.zero? } + Then { match.ip_protocol.zero? } Then { match.source_ip_address == '192.168.1.0' } Then { match.source_ip_address.prefixlen == 24 } Then { match.destination_ip_address == '0.0.0.0' } - Then { match.transport_source_port == 0 } - Then { match.transport_destination_port == 0 } + Then { match.transport_source_port.zero? } + Then { match.transport_destination_port.zero? } end end @@ -117,15 +117,15 @@ Then { match.in_port == 1 } Then { match.source_mac_address == '00:00:00:00:00:00' } Then { match.destination_mac_address == '00:00:00:00:00:00' } - Then { match.vlan_vid == 0 } - Then { match.vlan_priority == 0 } - Then { match.ether_type == 0 } - Then { match.tos == 0 } - Then { match.ip_protocol == 0 } + Then { match.vlan_vid.zero? } + Then { match.vlan_priority.zero? } + Then { match.ether_type.zero? } + Then { match.tos.zero? } + Then { match.ip_protocol.zero? } Then { match.source_ip_address == '0.0.0.0' } Then { match.destination_ip_address == '0.0.0.0' } - Then { match.transport_source_port == 0 } - Then { match.transport_destination_port == 0 } + Then { match.transport_source_port.zero? } + Then { match.transport_destination_port.zero? } describe '#==' do When(:result) { match == other } @@ -156,18 +156,18 @@ ] end Then { match.wildcards.fetch(:source_ip_address) == 8 } - Then { match.in_port == 0 } + Then { match.in_port.zero? } Then { match.source_mac_address == '00:00:00:00:00:00' } Then { match.destination_mac_address == '00:00:00:00:00:00' } - Then { match.vlan_vid == 0 } - Then { match.vlan_priority == 0 } - Then { match.ether_type == 0 } - Then { match.tos == 0 } - Then { match.ip_protocol == 0 } + Then { match.vlan_vid.zero? } + Then { match.vlan_priority.zero? } + Then { match.ether_type.zero? } + Then { match.tos.zero? } + Then { match.ip_protocol.zero? } Then { match.source_ip_address == '192.168.1.0/24' } Then { match.destination_ip_address == '0.0.0.0' } - Then { match.transport_source_port == 0 } - Then { match.transport_destination_port == 0 } + Then { match.transport_source_port.zero? } + Then { match.transport_destination_port.zero? } end context "with destination_ip_address: '192.168.1.0/24'" do @@ -189,18 +189,18 @@ ] end Then { match.wildcards.fetch(:destination_ip_address) == 8 } - Then { match.in_port == 0 } + Then { match.in_port.zero? } Then { match.source_mac_address == '00:00:00:00:00:00' } Then { match.destination_mac_address == '00:00:00:00:00:00' } - Then { match.vlan_vid == 0 } - Then { match.vlan_priority == 0 } - Then { match.ether_type == 0 } - Then { match.tos == 0 } - Then { match.ip_protocol == 0 } + Then { match.vlan_vid.zero? } + Then { match.vlan_priority.zero? } + Then { match.ether_type.zero? } + Then { match.tos.zero? } + Then { match.ip_protocol.zero? } Then { match.source_ip_address == '0.0.0.0' } Then { match.destination_ip_address == '192.168.1.0/24' } - Then { match.transport_source_port == 0 } - Then { match.transport_destination_port == 0 } + Then { match.transport_source_port.zero? } + Then { match.transport_destination_port.zero? } end end end diff --git a/spec/pio/open_flow10/packet_out_spec.rb b/spec/pio/open_flow10/packet_out_spec.rb index b9340446..d085ed4b 100644 --- a/spec/pio/open_flow10/packet_out_spec.rb +++ b/spec/pio/open_flow10/packet_out_spec.rb @@ -1,356 +1,7 @@ require 'pio/open_flow10/packet_out' -require 'pio/parse_error' -# rubocop:disable LineLength describe Pio::OpenFlow10::PacketOut do - Given(:header_dump) do - [ - 0x01, - 0x0d, - 0x00, 0x58, - 0x00, 0x00, 0x00, 0x16 - ].pack('C*') - end - - Given(:data_dump) do - [ - 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e, 0x01, 0x02, 0x03, 0x04, - 0x05, 0x06, 0x88, 0xcc, 0x02, 0x09, 0x07, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x23, 0x04, 0x05, 0x07, 0x00, 0x00, - 0x00, 0x0c, 0x06, 0x02, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 - ].pack('C*') - end - - Given(:body_dump) do - [ - 0xff, 0xff, 0xff, 0xff, - 0xff, 0xff, - 0x00, 0x08, - 0x00, 0x00, 0x00, 0x08, 0x00, 0x02, 0xff, 0xff - ].pack('C*') + data_dump - end - - describe '.read' do - When(:result) { Pio::OpenFlow10::PacketOut.read(binary) } - - context 'with a Packet-Out message' do - When(:binary) { header_dump + body_dump } - - Then { result.class == Pio::OpenFlow10::PacketOut } - Then { result.ofp_version == 0x1 } - Then { result.message_type == 0xd } - Then { result.message_length == 0x58 } - Then { result.transaction_id == 0x16 } - Then { result.xid == 0x16 } - - Then { result.buffer_id == 0xffffffff } - Then { result.in_port == 0xffff } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SendOutPort } - Then { result.actions[0].port == 2 } - Then { result.actions[0].max_length == 2**16 - 1 } - Then { result.raw_data.length == 64 } - end - - context 'with a Packet-Out message generated with PacketOut.new' do - When(:binary) do - Pio::OpenFlow10::PacketOut.new( - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SendOutPort.new(2), - raw_data: data_dump - ).to_binary - end - - Then { result.class == Pio::OpenFlow10::PacketOut } - Then { result.ofp_version == 0x1 } - Then { result.message_type == 0xd } - Then { result.message_length == 0x58 } - Then { result.transaction_id == 0x16 } - Then { result.xid == 0x16 } - - Then { result.buffer_id == 0xffffffff } - Then { result.in_port == 0xffff } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SendOutPort } - Then { result.actions[0].port == 2 } - Then { result.actions[0].max_length == 2**16 - 1 } - Then { result.raw_data.length == 64 } - end - - context 'with a Hello message' do - When(:binary) { [1, 0, 0, 8, 0, 0, 0, 0].pack('C*') } - - Then do - result == Failure(Pio::ParseError, - 'Invalid OpenFlow10 PacketOut message.') - end - end - end - describe '.new' do it_should_behave_like('an OpenFlow message', Pio::OpenFlow10::PacketOut) - - When(:result) { Pio::OpenFlow10::PacketOut.new(user_options) } - - context 'with a SendOutPort action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SendOutPort.new(2), - raw_data: data_dump - } - end - - Then { result.ofp_version == 0x1 } - Then { result.message_type == 0xd } - Then { result.message_length == 0x58 } - Then { result.transaction_id == 0x16 } - Then { result.xid == 0x16 } - - Then { result.buffer_id == 0xffffffff } - Then { result.in_port == 0xffff } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SendOutPort } - Then { result.actions[0].port == 2 } - Then { result.actions[0].max_length == 2**16 - 1 } - Then { result.raw_data.length == 64 } - - context '#to_binary' do - When(:binary) { result.to_binary } - - Then { binary == header_dump + body_dump } - end - end - - context 'with a SetVlanVid action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetVlanVid.new(10), - raw_data: data_dump - } - end - - Then { result.message_length == 0x58 } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetVlanVid } - Then { result.actions[0].vlan_id == 10 } - end - - context 'with a SetVlanPriority action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetVlanPriority.new(3), - raw_data: data_dump - } - end - - Then { result.message_length == 0x58 } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetVlanPriority } - Then { result.actions[0].vlan_priority == 3 } - end - - context 'with a StripVlanHeader action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::StripVlanHeader.new, - raw_data: data_dump - } - end - - Then { result.message_length == 0x58 } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::StripVlanHeader } - end - - context 'with a SetSourceMacAddress action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetSourceMacAddress.new('11:22:33:44:55:66'), - raw_data: data_dump - } - end - - Then { result.message_length == 0x60 } - Then { result.actions_len == 0x10 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetSourceMacAddress } - Then { result.actions[0].mac_address == '11:22:33:44:55:66' } - end - - context 'with a SetDestinationMacAddress action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetDestinationMacAddress.new('11:22:33:44:55:66'), - raw_data: data_dump - } - end - - Then { result.message_length == 0x60 } - Then { result.actions_len == 0x10 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetDestinationMacAddress } - Then { result.actions[0].mac_address == '11:22:33:44:55:66' } - end - - context 'with a SetSourceIpAddress action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetSourceIpAddress.new('1.2.3.4'), - raw_data: data_dump - } - end - - Then { result.message_length == 0x58 } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetSourceIpAddress } - Then { result.actions[0].ip_address == '1.2.3.4' } - end - - context 'with a SetDestinationIpAddress action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetDestinationIpAddress.new('1.2.3.4'), - raw_data: data_dump - } - end - - Then { result.message_length == 0x58 } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetDestinationIpAddress } - Then { result.actions[0].ip_address == '1.2.3.4' } - end - - context 'with a SetTos action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetTos.new(32), - raw_data: data_dump - } - end - - Then { result.message_length == 0x58 } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetTos } - Then { result.actions[0].type_of_service == 32 } - end - - context 'with a SetTransportSourcePort action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetTransportSourcePort.new(100), - raw_data: data_dump - } - end - - Then { result.message_length == 0x58 } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetTransportSourcePort } - Then { result.actions[0].port == 100 } - end - - context 'with a SetTransportDestinationPort action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::SetTransportDestinationPort.new(100), - raw_data: data_dump - } - end - - Then { result.message_length == 0x58 } - Then { result.actions_len == 0x8 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SetTransportDestinationPort } - Then { result.actions[0].port == 100 } - end - - context 'with a Enqueue action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: Pio::OpenFlow10::Enqueue.new(port: 1, queue_id: 2), - raw_data: data_dump - } - end - - Then { result.message_length == 0x60 } - Then { result.actions_len == 0x10 } - Then { result.actions.length == 1 } - Then { result.actions[0].is_a? Pio::OpenFlow10::Enqueue } - Then { result.actions[0].port == 1 } - Then { result.actions[0].queue_id == 2 } - end - - context 'with SendOutPort and SetVlanVid action' do - When(:user_options) do - { - transaction_id: 0x16, - buffer_id: 0xffffffff, - in_port: 0xffff, - actions: [Pio::OpenFlow10::SendOutPort.new(2), - Pio::OpenFlow10::SetVlanVid.new(10)], - raw_data: data_dump - } - end - - Then { result.message_length == 0x60 } - Then { result.actions_len == 0x10 } - Then { result.actions.length == 2 } - Then { result.actions[0].is_a? Pio::OpenFlow10::SendOutPort } - Then { result.actions[0].port == 2 } - Then { result.actions[0].max_length == 2**16 - 1 } - Then { result.actions[1].is_a? Pio::OpenFlow10::SetVlanVid } - Then { result.actions[1].vlan_id == 10 } - end end end -# rubocop:enable LineLength diff --git a/spec/pio/open_flow10/phy_port16_spec.rb b/spec/pio/open_flow10/phy_port16_spec.rb index 4d7cd3e5..38e36fac 100644 --- a/spec/pio/open_flow10/phy_port16_spec.rb +++ b/spec/pio/open_flow10/phy_port16_spec.rb @@ -3,16 +3,16 @@ describe Pio::OpenFlow10::PhyPort16 do describe '.new' do When(:phy_port) do - Pio::OpenFlow10::PhyPort16.new(port_no: 1, - hardware_address: '11:22:33:44:55:66', + Pio::OpenFlow10::PhyPort16.new(number: 1, + mac_address: '11:22:33:44:55:66', name: 'port123', config: [:port_down], state: [:link_down], curr: [:port_10gb_fd, :port_copper]) end - Then { phy_port.port_no == 1 } - Then { phy_port.hardware_address == '11:22:33:44:55:66' } + Then { phy_port.number == 1 } + Then { phy_port.mac_address == '11:22:33:44:55:66' } Then { phy_port.name == 'port123' } Then { phy_port.config == [:port_down] } Then { phy_port.state == [:link_down] } @@ -20,6 +20,6 @@ Then { phy_port.advertised.empty? } Then { phy_port.supported.empty? } Then { phy_port.peer.empty? } - Then { phy_port.to_binary_s.length > 0 } + Then { !phy_port.to_binary_s.empty? } end end diff --git a/spec/pio/open_flow13/error/hello_failed_spec.rb b/spec/pio/open_flow13/error/hello_failed_spec.rb index 013f8032..d149daad 100644 --- a/spec/pio/open_flow13/error/hello_failed_spec.rb +++ b/spec/pio/open_flow13/error/hello_failed_spec.rb @@ -10,7 +10,7 @@ context 'with {}' do Given(:options) { {} } - Then { hello_failed.message_length == 12 } + Then { hello_failed.length == 12 } Then { hello_failed.error_type == :hello_failed } Then { hello_failed.error_code == :incompatible } Then { hello_failed.description == '' } @@ -19,7 +19,7 @@ context "with description: 'error description'" do Given(:options) { { description: 'error description' } } - Then { hello_failed.message_length == 29 } + Then { hello_failed.length == 29 } Then { hello_failed.description == 'error description' } end end diff --git a/spec/pio/open_flow13/goto_table_spec.rb b/spec/pio/open_flow13/goto_table_spec.rb index ec75eeb3..ff7fac6f 100644 --- a/spec/pio/open_flow13/goto_table_spec.rb +++ b/spec/pio/open_flow13/goto_table_spec.rb @@ -1,8 +1,8 @@ require 'pio/open_flow13/goto_table' -describe Pio::GotoTable do +describe Pio::OpenFlow13::GotoTable do describe '.new' do - When(:goto_table) { Pio::GotoTable.new(options) } + When(:goto_table) { Pio::OpenFlow13::GotoTable.new(options) } context 'with 1' do Given(:options) { 1 } diff --git a/spec/pio/open_flow13/hello_spec.rb b/spec/pio/open_flow13/hello_spec.rb index 401cf163..1e988dd6 100644 --- a/spec/pio/open_flow13/hello_spec.rb +++ b/spec/pio/open_flow13/hello_spec.rb @@ -9,11 +9,11 @@ Given(:binary) { [4, 0, 0, 8, 0, 0, 0, 0].pack('C*') } Then { result.class == Pio::OpenFlow13::Hello } - 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.version == 4 } + Then { result.type.zero? } + Then { result.length == 8 } + Then { result.transaction_id.zero? } + Then { result.xid.zero? } Then { result.supported_versions.empty? } end @@ -23,11 +23,11 @@ end Then { result.class == Pio::OpenFlow13::Hello } - 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.version == 4 } + Then { result.type.zero? } + Then { result.length == 16 } + Then { result.transaction_id.zero? } + Then { result.xid.zero? } Then { result.supported_versions == [:open_flow10, :open_flow13] } end @@ -46,11 +46,11 @@ context 'with no arguments' do When(:result) { Pio::OpenFlow13::Hello.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.version == 4 } + Then { result.type.zero? } + Then { result.length == 16 } + Then { result.transaction_id.zero? } + Then { result.xid.zero? } Then { result.supported_versions == [:open_flow13] } Then do result.to_binary == diff --git a/spec/pio/open_flow13/match_spec.rb b/spec/pio/open_flow13/match_spec.rb index 45721698..fdef21b8 100644 --- a/spec/pio/open_flow13/match_spec.rb +++ b/spec/pio/open_flow13/match_spec.rb @@ -386,7 +386,7 @@ end Then { match.ether_type == 0x0800 } Then { match.ip_protocol == 1 } - Then { match.icmpv4_code == 0 } + Then { match.icmpv4_code.zero? } And { match.class == Pio::OpenFlow13::Match } And { match.length == 24 } And { match.match_type == Pio::OpenFlow13::MATCH_TYPE_OXM } @@ -696,15 +696,15 @@ end def read_raw_data_file(name) - IO.read File.join(__dir__, '..', '..', '..', name) + IO.read File.join(__dir__, '..', '..', '..', 'fixtures', name) end describe '.read' do When(:match) { Pio::OpenFlow13::Match.read(raw_data) } - context 'with file "features/open_flow13/oxm_no_fields.raw"' do + context 'with file "open_flow13/oxm_no_fields.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_no_fields.raw' + read_raw_data_file 'open_flow13/oxm_no_fields.raw' end Then { match.match_fields == [] } And { match.class == Pio::OpenFlow13::Match } @@ -713,9 +713,9 @@ def read_raw_data_file(name) And { match.match_length == 4 } end - context 'with file "features/open_flow13/oxm_in_port_field.raw"' do + context 'with file "open_flow13/oxm_in_port_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_in_port_field.raw' + read_raw_data_file 'open_flow13/oxm_in_port_field.raw' end Then { match.in_port == 1 } And { match.class == Pio::OpenFlow13::Match } @@ -735,9 +735,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 4 } end - context 'with file "features/open_flow13/oxm_ether_destination_field.raw"' do + context 'with file "open_flow13/oxm_ether_destination_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_ether_destination_field.raw' + read_raw_data_file 'open_flow13/oxm_ether_destination_field.raw' end Then { match.destination_mac_address == 'ff:ff:ff:ff:ff:ff' } And { match.class == Pio::OpenFlow13::Match } @@ -757,9 +757,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 6 } end - context 'with file "features/open_flow13/oxm_ether_source_field.raw"' do + context 'with file "open_flow13/oxm_ether_source_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_ether_source_field.raw' + read_raw_data_file 'open_flow13/oxm_ether_source_field.raw' end Then { match.source_mac_address == '01:02:03:04:05:06' } And { match.class == Pio::OpenFlow13::Match } @@ -779,9 +779,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 6 } end - context 'with file "features/open_flow13/oxm_masked_ether_destination_field.raw"' do + context 'with file "open_flow13/oxm_masked_ether_destination_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_masked_ether_destination_field.raw' + read_raw_data_file 'open_flow13/oxm_masked_ether_destination_field.raw' end Then { match.destination_mac_address == 'ff:ff:ff:ff:ff:ff' } Then { match.destination_mac_address_mask == 'ff:ff:ff:00:00:00' } @@ -802,9 +802,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 12 } end - context 'with file "features/open_flow13/oxm_masked_ether_source_field.raw"' do + context 'with file "open_flow13/oxm_masked_ether_source_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_masked_ether_source_field.raw' + read_raw_data_file 'open_flow13/oxm_masked_ether_source_field.raw' end Then { match.source_mac_address == '01:02:03:04:05:06' } Then { match.source_mac_address_mask == 'ff:ff:ff:00:00:00' } @@ -825,11 +825,11 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 12 } end - context 'with file "features/open_flow13/oxm_ether_type_field.raw"' do + context 'with file "open_flow13/oxm_ether_type_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_ether_type_field.raw' + read_raw_data_file 'open_flow13/oxm_ether_type_field.raw' end - Then { match.ether_type == 0 } + Then { match.ether_type.zero? } And { match.class == Pio::OpenFlow13::Match } And { match.length == 16 } And { match.match_type == Pio::OpenFlow13::MATCH_TYPE_OXM } @@ -847,9 +847,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 2 } end - context 'with file "features/open_flow13/oxm_vlan_vid_field.raw"' do + context 'with file "open_flow13/oxm_vlan_vid_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_vlan_vid_field.raw' + read_raw_data_file 'open_flow13/oxm_vlan_vid_field.raw' end Then { match.vlan_vid == 10 } And { match.class == Pio::OpenFlow13::Match } @@ -869,9 +869,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 2 } end - context 'with file "features/open_flow13/oxm_vlan_pcp_field.raw"' do + context 'with file "open_flow13/oxm_vlan_pcp_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_vlan_pcp_field.raw' + read_raw_data_file 'open_flow13/oxm_vlan_pcp_field.raw' end Then { match.vlan_pcp == 5 } And { match.class == Pio::OpenFlow13::Match } @@ -891,9 +891,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 2 } end - context 'with file "features/open_flow13/oxm_ip_dscp_field.raw"' do + context 'with file "open_flow13/oxm_ip_dscp_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_ip_dscp_field.raw' + read_raw_data_file 'open_flow13/oxm_ip_dscp_field.raw' end Then { match.ip_dscp == 0x2e } And { match.class == Pio::OpenFlow13::Match } @@ -913,9 +913,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 2 } end - context 'with file "features/open_flow13/oxm_ip_ecn_field.raw"' do + context 'with file "open_flow13/oxm_ip_ecn_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_ip_ecn_field.raw' + read_raw_data_file 'open_flow13/oxm_ip_ecn_field.raw' end Then { match.ip_ecn == 3 } And { match.class == Pio::OpenFlow13::Match } @@ -935,9 +935,9 @@ def read_raw_data_file(name) And { match.match_fields[0].oxm_length == 2 } end - context 'with file "features/open_flow13/oxm_ipv4_source_field.raw"' do + context 'with file "open_flow13/oxm_ipv4_source_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_ipv4_source_field.raw' + read_raw_data_file 'open_flow13/oxm_ipv4_source_field.raw' end Then { match.ether_type == 0x0800 } Then { match.ipv4_source_address == '1.2.3.4' } @@ -968,9 +968,9 @@ def read_raw_data_file(name) And { match.match_fields[1].oxm_length == 4 } end - context 'with file "features/open_flow13/oxm_ipv4_destination_field.raw"' do + context 'with file "open_flow13/oxm_ipv4_destination_field.raw"' do Given(:raw_data) do - read_raw_data_file 'features/open_flow13/oxm_ipv4_destination_field.raw' + read_raw_data_file 'open_flow13/oxm_ipv4_destination_field.raw' end Then { match.ether_type == 0x0800 } Then { match.ipv4_destination_address == '11.22.33.44' } diff --git a/spec/pio/open_flow13/meter_spec.rb b/spec/pio/open_flow13/meter_spec.rb index 6b961466..b3450536 100644 --- a/spec/pio/open_flow13/meter_spec.rb +++ b/spec/pio/open_flow13/meter_spec.rb @@ -1,8 +1,8 @@ require 'pio/open_flow13/meter' -describe Pio::Meter do +describe Pio::OpenFlow13::Meter do describe '.new' do - When(:meter) { Pio::Meter.new(options) } + When(:meter) { Pio::OpenFlow13::Meter.new(options) } context 'with 1' do Given(:options) { 1 } diff --git a/spec/pio/open_flow13/nicira_reg_load_spec.rb b/spec/pio/open_flow13/nicira_reg_load_spec.rb new file mode 100644 index 00000000..6e024c05 --- /dev/null +++ b/spec/pio/open_flow13/nicira_reg_load_spec.rb @@ -0,0 +1,71 @@ +require 'pio/open_flow13/nicira_reg_load' + +describe Pio::OpenFlow13::NiciraRegLoad do + describe '.new' do + When(:nicira_reg_load) do + Pio::OpenFlow13::NiciraRegLoad.new(value, destination, options) + end + + context 'with 0xdeadbeef, :reg0' do + Given(:value) { 0xdeadbeef } + Given(:destination) { :reg0 } + Given(:options) { {} } + + Invariant do + nicira_reg_load.n_bits == nicira_reg_load._destination[:oxm_length] * 8 + end + + Then { nicira_reg_load.action_type == 0xffff } + Then { nicira_reg_load.action_length == 24 } + Then { nicira_reg_load.vendor == 0x2320 } + Then { nicira_reg_load.subtype == 7 } + Then { nicira_reg_load.offset.zero? } + Then { nicira_reg_load.n_bits == 32 } + Then { nicira_reg_load.destination == :reg0 } + Then { nicira_reg_load._destination[:oxm_class] == 1 } + Then { nicira_reg_load._destination[:oxm_field].zero? } + Then { nicira_reg_load._destination[:oxm_length] == 4 } + Then { nicira_reg_load.value == 0xdeadbeef } + end + + context 'with 0xdeadbeef, :metadata' do + Given(:value) { 0xdeadbeef } + Given(:destination) { :metadata } + Given(:options) { {} } + + Invariant do + nicira_reg_load.n_bits == nicira_reg_load._destination[:oxm_length] * 8 + end + + Then { nicira_reg_load.action_type == 0xffff } + Then { nicira_reg_load.action_length == 24 } + Then { nicira_reg_load.vendor == 0x2320 } + Then { nicira_reg_load.subtype == 7 } + Then { nicira_reg_load.offset.zero? } + Then { nicira_reg_load.n_bits == 64 } + Then { nicira_reg_load.destination == :metadata } + Then { nicira_reg_load._destination[:oxm_class] == 0x8000 } + Then { nicira_reg_load._destination[:oxm_field] == 2 } + Then { nicira_reg_load._destination[:oxm_length] == 8 } + Then { nicira_reg_load.value == 0xdeadbeef } + end + + context 'with 0xdeadbeef, :reg0, offset: 16, n_bits: 16' do + Given(:value) { 0xdeadbeef } + Given(:destination) { :reg0 } + Given(:options) { { offset: 16, n_bits: 16 } } + + Then { nicira_reg_load.action_type == 0xffff } + Then { nicira_reg_load.action_length == 24 } + Then { nicira_reg_load.vendor == 0x2320 } + Then { nicira_reg_load.subtype == 7 } + Then { nicira_reg_load.offset == 16 } + Then { nicira_reg_load.n_bits == 16 } + Then { nicira_reg_load.destination == :reg0 } + Then { nicira_reg_load._destination[:oxm_class] == 1 } + Then { nicira_reg_load._destination[:oxm_field].zero? } + Then { nicira_reg_load._destination[:oxm_length] == 4 } + Then { nicira_reg_load.value == 0xdeadbeef } + end + end +end diff --git a/spec/pio/open_flow13/nicira_reg_move_spec.rb b/spec/pio/open_flow13/nicira_reg_move_spec.rb new file mode 100644 index 00000000..68fee624 --- /dev/null +++ b/spec/pio/open_flow13/nicira_reg_move_spec.rb @@ -0,0 +1,40 @@ +require 'pio/open_flow13/nicira_reg_move' + +describe Pio::OpenFlow13::NiciraRegMove do + describe '.new' do + When(:nicira_reg_move) do + Pio::OpenFlow13::NiciraRegMove.new(options) + end + + context 'with source: :arp_sender_hardware_address,'\ + ' destination: :arp_target_hardware_address' do + Given(:options) do + { source: :arp_sender_hardware_address, + destination: :arp_target_hardware_address } + end + + Invariant do + nicira_reg_move.n_bits == + nicira_reg_move._source[:oxm_length] * 8 + end + + Then { nicira_reg_move.action_type == 0xffff } + Then { nicira_reg_move.action_length == 24 } + Then { nicira_reg_move.vendor == 0x2320 } + Then { nicira_reg_move.subtype == 6 } + Then { nicira_reg_move.n_bits == 48 } + Then { nicira_reg_move.source_offset.zero? } + Then { nicira_reg_move.destination_offset.zero? } + Then { nicira_reg_move.source == :arp_sender_hardware_address } + Then { nicira_reg_move._source[:oxm_class] == 0x8000 } + Then { nicira_reg_move._source[:oxm_field] == 24 } + Then { nicira_reg_move._source[:oxm_hasmask].zero? } + Then { nicira_reg_move._source[:oxm_length] == 6 } + Then { nicira_reg_move.destination == :arp_target_hardware_address } + Then { nicira_reg_move._destination[:oxm_class] == 0x8000 } + Then { nicira_reg_move._destination[:oxm_field] == 25 } + Then { nicira_reg_move._destination[:oxm_hasmask].zero? } + Then { nicira_reg_move._destination[:oxm_length] == 6 } + end + end +end diff --git a/spec/pio/open_flow13/nicira_send_out_port_spec.rb b/spec/pio/open_flow13/nicira_send_out_port_spec.rb new file mode 100644 index 00000000..f21e5774 --- /dev/null +++ b/spec/pio/open_flow13/nicira_send_out_port_spec.rb @@ -0,0 +1,29 @@ +require 'pio/open_flow13/nicira_send_out_port' + +describe Pio::OpenFlow13::NiciraSendOutPort do + describe '.new' do + When(:nicira_send_out_port) do + Pio::OpenFlow13::NiciraSendOutPort.new(source) + end + + context 'with :reg0' do + Given(:source) { :reg0 } + + Invariant do + nicira_send_out_port.n_bits == + nicira_send_out_port._source[:oxm_length] * 8 + end + + Then { nicira_send_out_port.action_type == 0xffff } + Then { nicira_send_out_port.action_length == 24 } + Then { nicira_send_out_port.vendor == 0x2320 } + Then { nicira_send_out_port.subtype == 15 } + Then { nicira_send_out_port.offset.zero? } + Then { nicira_send_out_port.n_bits == 32 } + Then { nicira_send_out_port.source == :reg0 } + Then { nicira_send_out_port._source[:oxm_class] == 1 } + Then { nicira_send_out_port._source[:oxm_field].zero? } + Then { nicira_send_out_port._source[:oxm_length] == 4 } + end + end +end diff --git a/spec/pio/open_flow13/write_metadata_spec.rb b/spec/pio/open_flow13/write_metadata_spec.rb index 91799758..3a3a17ea 100644 --- a/spec/pio/open_flow13/write_metadata_spec.rb +++ b/spec/pio/open_flow13/write_metadata_spec.rb @@ -1,8 +1,8 @@ require 'pio/open_flow13/write_metadata' -describe Pio::WriteMetadata do +describe Pio::OpenFlow13::WriteMetadata do describe '.new' do - When(:write_metadata) { Pio::WriteMetadata.new(options) } + When(:write_metadata) { Pio::OpenFlow13::WriteMetadata.new(options) } context 'with metadata: 1' do Given(:options) do @@ -11,7 +11,7 @@ } end Then { write_metadata.metadata == 1 } - Then { write_metadata.metadata_mask == 0 } + Then { write_metadata.metadata_mask.zero? } end context 'with metadata: 1, metadata_mask: 1' do diff --git a/spec/pio/open_flow_spec.rb b/spec/pio/open_flow_spec.rb index 5d0931fd..658bcf1f 100644 --- a/spec/pio/open_flow_spec.rb +++ b/spec/pio/open_flow_spec.rb @@ -2,32 +2,32 @@ describe Pio::OpenFlow do describe 'switch_version' do - When(:error) { Pio::OpenFlow.switch_version(version) } + When(:result) { Pio::OpenFlow.version = version } context 'with :OpenFlow10' do Given(:version) { :OpenFlow10 } - Then { Pio::OpenFlow.version == 'OpenFlow10' } + Then { Pio::OpenFlow.version == :OpenFlow10 } end context 'with "OpenFlow10"' do Given(:version) { 'OpenFlow10' } - Then { Pio::OpenFlow.version == 'OpenFlow10' } + Then { Pio::OpenFlow.version == :OpenFlow10 } end context 'with :OpenFlow13' do Given(:version) { :OpenFlow13 } - Then { Pio::OpenFlow.version == 'OpenFlow13' } + Then { Pio::OpenFlow.version == :OpenFlow13 } end context 'with "OpenFlow13"' do Given(:version) { 'OpenFlow13' } - Then { Pio::OpenFlow.version == 'OpenFlow13' } + Then { Pio::OpenFlow.version == :OpenFlow13 } end context 'with :OpenFlow100' do Given(:version) { :OpenFlow100 } Then do - error == Failure(RuntimeError, 'OpenFlow100 is not supported yet.') + result == Failure(RuntimeError, 'OpenFlow100 is not supported yet.') end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 77b6ce10..975d0ba6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -8,7 +8,7 @@ formatters << CodeClimate::TestReporter::Formatter end -SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[*formatters] +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new(formatters) SimpleCov.start { add_filter '/vendor/' } require 'rspec/given' diff --git a/spec/support/shared_examples_for_openflow_messages.rb b/spec/support/shared_examples_for_openflow_messages.rb index a61338b0..2d95b335 100644 --- a/spec/support/shared_examples_for_openflow_messages.rb +++ b/spec/support/shared_examples_for_openflow_messages.rb @@ -38,18 +38,6 @@ end end -shared_examples 'an OpenFlow message with no body' do |klass| - describe '.new' do - When(:message) { klass.new(options) } - - context "with body: 'DEADBEEF'" do - When(:options) { { body: 'DEADBEEF' } } - - Then { message.body == '' } - end - end -end - shared_examples 'an OpenFlow message with Datapath ID' do |klass| When(:message) { klass.new(options) } @@ -63,14 +51,18 @@ context 'with { datapath_id: 0 }' do Given(:options) { { datapath_id: 0 } } - Then { message.datapath_id == 0 } + Then { message.datapath_id.zero? } + And { message.dpid.zero? } And { message.datapath_id.is_a? Integer } + And { message.dpid.is_a? Integer } end context 'with { datapath_id: 2**64 - 1 }' do Given(:options) { { datapath_id: 2**64 - 1 } } Then { message.datapath_id == 2**64 - 1 } + And { message.dpid == 2**64 - 1 } And { message.datapath_id.is_a? Integer } + And { message.dpid.is_a? Integer } end context 'with { datapath_id: 2**64 }' do diff --git a/tasks/LICENSE b/tasks/LICENSE deleted file mode 100644 index 6b156fe1..00000000 --- a/tasks/LICENSE +++ /dev/null @@ -1,675 +0,0 @@ -GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - {one line to give the program's name and a brief idea of what it does.} - Copyright (C) {year} {name of author} - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - {project} Copyright (C) {year} {fullname} - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. -