commit a9da643fb2269f18f931cc81ae26573feb5d41e5 Author: Srivats P. Date: Thu Apr 16 22:20:07 2015 +0530 Feature: Reserve Ports for cooperative use of shared drone. If a port is reserved the GUI will display the username of the owner but others can still view and control the port; in other words, we just potentially prevent unintentional usage of other people's ports, users still need to play nice with each other! Fixes issue 144 diff --git a/.hgignore b/.hgignore new file mode 100644 index 0000000..48fc886 --- /dev/null +++ b/.hgignore @@ -0,0 +1,38 @@ +syntax: glob + +# generated object files +*.pyc +*.o +*.a +*.dll +*.so +*.exe +*.app +drone +ostinato + +# Qt generated files +ui_*.h +moc_*.cpp +qrc_*.cpp + +# QMake generated files +Makefile* +*\object_script.* + +# protobuf generated files +*.pb.h +*.pb.cc +*_pb2.py + +# ostinato generated files +version.cpp +pkg_info.json + +# vim swap files +*.swp +.DS_Store + +# ctags +tags + diff --git a/.vimrc b/.vimrc new file mode 100644 index 0000000..fd28004 --- /dev/null +++ b/.vimrc @@ -0,0 +1,5 @@ +set shiftwidth=4 +set tabstop=8 +set softtabstop=4 +set expandtab +set cindent diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + 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. + + + Copyright (C) + + 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: + + Copyright (C) + 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/binding/LICENSE.txt b/binding/LICENSE.txt new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/binding/LICENSE.txt @@ -0,0 +1,674 @@ + 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. + + + Copyright (C) + + 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: + + Copyright (C) + 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/binding/README.txt b/binding/README.txt new file mode 100644 index 0000000..b0de5ec --- /dev/null +++ b/binding/README.txt @@ -0,0 +1,9 @@ +=============== +python-ostinato +=============== + +python-ostinato provides python bindings for Ostinato. + +Ostinato is a network packet/traffic generator and analyzer. It aims to be "Wireshark in Reverse" and become complementary to Wireshark. + +Documentation is available in the wiki at http://ostinato.org diff --git a/binding/__init__.py b/binding/__init__.py new file mode 100644 index 0000000..8c8376d --- /dev/null +++ b/binding/__init__.py @@ -0,0 +1,28 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This 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 + +import json +import logging +from os.path import dirname + +with open(dirname(__file__) + '/pkg_info.json') as f: + _info = json.load(f) + +__version__ = _info['version'] +__revision__ = _info['revision'] + +__log__ = logging.getLogger(__name__) diff --git a/binding/binding.pro b/binding/binding.pro new file mode 100644 index 0000000..68b7a58 --- /dev/null +++ b/binding/binding.pro @@ -0,0 +1,4 @@ +TEMPLATE = lib +CONFIG += pkg_info + +include(../version.pri) diff --git a/binding/core.py b/binding/core.py new file mode 100644 index 0000000..d8de580 --- /dev/null +++ b/binding/core.py @@ -0,0 +1,67 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This 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 + +import os +from rpc import OstinatoRpcChannel, OstinatoRpcController, RpcError +import protocols.protocol_pb2 as ost_pb +from __init__ import __version__ + +class DroneProxy(object): + + def __init__(self, host_name, port_number=7878): + self.host = host_name + self.port = port_number + self.channel = OstinatoRpcChannel() + self.stub = ost_pb.OstService_Stub(self.channel) + self.void = ost_pb.Void() + + for method in self.stub.GetDescriptor().methods: + fn = lambda request=self.void, method_name=method.name: \ + self.callRpcMethod(method_name, request) + self.__dict__[method.name] = fn + + def hostName(self): + return self.host + + def portNumber(self): + return self.port + + def connect(self): + self.channel.connect(self.host, self.port) + ver = ost_pb.VersionInfo() + ver.version = __version__ + compat = self.checkVersion(ver) + if compat.result == ost_pb.VersionCompatibility.kIncompatible: + raise RpcError('incompatible version %s (%s)' % + (ver.version, compat.notes)) + + def disconnect(self): + self.channel.disconnect() + + def callRpcMethod(self, method_name, request): + controller = OstinatoRpcController() + ost_pb.OstService_Stub.__dict__[method_name]( + self.stub, controller, request, None) + return controller.response + + def saveCaptureBuffer(self, buffer, file_name): + f= open(file_name, 'wb') + f.write(buffer) + f.flush() + os.fsync(f.fileno()) + f.close() + diff --git a/binding/example.py b/binding/example.py new file mode 100644 index 0000000..0a77a27 --- /dev/null +++ b/binding/example.py @@ -0,0 +1,188 @@ +#! /usr/bin/env python + +# standard modules +import logging +import os +import sys +import time + +# ostinato modules +# (user scripts using the installed package should prepend ostinato. i.e +# ostinato.core and ostinato.protocols) +from core import ost_pb, DroneProxy +from protocols.mac_pb2 import mac +from protocols.ip4_pb2 import ip4, Ip4 + +# initialize defaults +use_defaults = False +host_name = '127.0.0.1' +tx_port_number = 0 +rx_port_number = 0 + +# setup logging +log = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + +# command-line option/arg processing +if len(sys.argv) > 1: + if sys.argv[1] in ('-d', '--use-defaults'): + use_defaults = True + if sys.argv[1] in ('-h', '--help'): + print('%s [OPTION]...' % (sys.argv[0])) + print('Options:') + print(' -d --use-defaults run using default values') + print(' -h --help show this help') + sys.exit(0) + +print('') +print('This example expects the following topology -') +print('') +print(' +-------+ +-------+') +print(' | |Tx--->----| |') +print(' | Drone | | DUT |') +print(' | |Rx---<----| |') +print(' +-------+ +-------+') +print('') +print('Drone has 2 ports connected to DUT. Packets sent on the Tx port') +print('are expected to be received back on the Rx port') +print('') +print('An easy way to simulate the above topology is to select the loopback') +print('port as both Tx and Rx ports') +print('') + +if not use_defaults: + s = raw_input('Drone\'s Hostname/IP [%s]: ' % (host_name)) + host_name = s or host_name + +drone = DroneProxy(host_name) + +try: + # connect to drone + log.info('connecting to drone(%s:%d)' + % (drone.hostName(), drone.portNumber())) + drone.connect() + + # retreive port id list + log.info('retreiving port list') + port_id_list = drone.getPortIdList() + + # retreive port config list + log.info('retreiving port config for all ports') + port_config_list = drone.getPortConfig(port_id_list) + + if len(port_config_list.port) == 0: + log.warning('drone has no ports!') + sys.exit(1) + + # print port list and get tx/rx port id + print('Port List') + print('---------') + for port in port_config_list.port: + print('%d.%s (%s)' % (port.port_id.id, port.name, port.description)) + # use a loopback port as default tx/rx port + if ('lo' in port.name or 'loopback' in port.description.lower()): + tx_port_number = port.port_id.id + rx_port_number = port.port_id.id + + if not use_defaults: + p = raw_input('Tx Port Id [%d]: ' % (tx_port_number)) + if p: + tx_port_number = int(p) + + p = raw_input('Rx Port Id [%d]: ' % (rx_port_number)) + if p: + rx_port_number = int(p) + + tx_port = ost_pb.PortIdList() + tx_port.port_id.add().id = tx_port_number; + + rx_port = ost_pb.PortIdList() + rx_port.port_id.add().id = rx_port_number; + + # add a stream + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(tx_port.port_id[0]) + stream_id.stream_id.add().id = 1 + log.info('adding tx_stream %d' % stream_id.stream_id[0].id) + drone.addStream(stream_id) + + # configure the stream + stream_cfg = ost_pb.StreamConfigList() + stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[0].id + s.core.is_enabled = True + s.control.num_packets = 5 + + # setup stream protocols as mac:eth2:ip4:udp:payload + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber + p.Extensions[mac].dst_mac = 0x001122334455 + p.Extensions[mac].src_mac = 0x00aabbccddee + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + # reduce typing by creating a shorter reference to p.Extensions[ip4] + ip = p.Extensions[ip4] + ip.src_ip = 0x01020304 + ip.dst_ip = 0x05060708 + ip.dst_ip_mode = Ip4.e_im_inc_host + + s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber + s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + + log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) + drone.modifyStream(stream_cfg) + + # clear tx/rx stats + log.info('clearing tx/rx stats') + drone.clearStats(tx_port) + drone.clearStats(rx_port) + + # start capture and transmit + log.info('starting capture') + drone.startCapture(rx_port) + log.info('starting transmit') + drone.startTransmit(tx_port) + + # wait for transmit to finish + log.info('waiting for transmit to finish ...') + time.sleep(7) + + # stop transmit and capture + log.info('stopping transmit') + drone.stopTransmit(tx_port) + log.info('stopping capture') + drone.stopCapture(rx_port) + + # get tx/rx stats + log.info('retreiving stats') + tx_stats = drone.getStats(tx_port) + rx_stats = drone.getStats(rx_port) + + #log.info('--> (tx_stats)' + tx_stats.__str__()) + #log.info('--> (rx_stats)' + rx_stats.__str__()) + log.info('tx pkts = %d, rx pkts = %d' % + (tx_stats.port_stats[0].tx_pkts, rx_stats.port_stats[0].rx_pkts)) + + # retrieve and dump received packets + log.info('getting Rx capture buffer') + buff = drone.getCaptureBuffer(rx_port.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer') + os.system('tshark -r capture.pcap') + os.remove('capture.pcap') + + # delete streams + log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) + drone.deleteStream(stream_id) + + # bye for now + drone.disconnect() + +except Exception as ex: + log.exception(ex) + sys.exit(1) diff --git a/binding/protocols/__init__.py b/binding/protocols/__init__.py new file mode 100644 index 0000000..161ae68 --- /dev/null +++ b/binding/protocols/__init__.py @@ -0,0 +1,17 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This 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 + diff --git a/binding/rpc.py b/binding/rpc.py new file mode 100644 index 0000000..2eb721b --- /dev/null +++ b/binding/rpc.py @@ -0,0 +1,167 @@ +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This 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 + +from google.protobuf.message import EncodeError, DecodeError +from google.protobuf.service import RpcChannel +from google.protobuf.service import RpcController +import logging +import socket +import struct +import sys + +class PeerClosedConnError(Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return self.msg + +class RpcError(Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return self.msg + +class RpcMismatchError(Exception): + def __init__(self, msg, received, expected): + self.msg = msg + self.received = received + self.expected = expected + def __str__(self): + return '%s - Expected method %d, Received method %d' % ( + self.msg, self.expected, self.received) + +class OstinatoRpcController(RpcController): + def __init__(self): + super(OstinatoRpcController, self).__init__() + +class OstinatoRpcChannel(RpcChannel): + def __init__(self): + self.log = logging.getLogger(__name__) + self.log.debug('opening socket') + + def connect(self, host, port): + self.peer = '%s:%d' % (host, port) + self.log.debug('connecting to %s', self.peer) + try: + self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.sock.connect((host, port)) + except socket.error as e: + error = 'ERROR: Unable to connect to Drone %s (%s)' % ( + self.peer, str(e)) + print(error) + raise + + def disconnect(self): + self.log.debug('closing socket') + self.sock.close() + + def CallMethod(self, method, controller, request, response_class, done): + MSG_HDR_SIZE = 8 + MSG_TYPE_REQUEST = 1 + MSG_TYPE_RESPONSE = 2 + MSG_TYPE_BLOB = 3 + MSG_TYPE_ERROR = 4 + + error = '' + try: + self.log.info('invoking RPC %s(%s): %s', method.name, + type(request).__name__, response_class.__name__) + self.log.debug('serializing request arg %s', request) + req = request.SerializeToString() + hdr = struct.pack('>HHI', MSG_TYPE_REQUEST, method.index, len(req)) + self.log.debug('req.hdr = %r', hdr) + self.sock.sendall(hdr + req) + + # receive and parse header + self.log.debug('receiving response hdr') + hdr = '' + while len(hdr) < MSG_HDR_SIZE: + chunk = self.sock.recv(MSG_HDR_SIZE - len(hdr)) + if chunk == '': + raise PeerClosedConnError('connection closed by peer') + hdr = hdr + chunk + + (msg_type, method_index, resp_len) = struct.unpack('>HHI', hdr) + self.log.debug('resp hdr: type = %d, method = %d, len = %d', + msg_type, method_index, resp_len) + + # receive and parse the actual response message + self.log.debug('receiving response data') + resp = '' + while len(resp) < resp_len: + chunk = self.sock.recv(resp_len - len(resp)) + if chunk == '': + raise PeerClosedConnError('connection closed by peer') + resp = resp + chunk + + # verify response method is same as the one requested + if method_index != method.index: + raise RpcMismatchError('RPC mismatch', + expected = method.index, received = method_index) + + if msg_type == MSG_TYPE_RESPONSE: + response = response_class() + response.ParseFromString(resp) + self.log.debug('parsed response %s', response) + elif msg_type == MSG_TYPE_BLOB: + response = resp + elif msg_type == MSG_TYPE_ERROR: + raise RpcError(unicode(resp, 'utf-8')) + else: + raise RpcError('unknown RPC msg type %d' % msg_type) + + controller.response = response + + except socket.error as e: + error = 'ERROR: RPC %s() to Drone %s failed (%s)' % ( + method.name, self.peer, e) + self.log.exception(error+e) + raise + except PeerClosedConnError as e: + error = 'ERROR: Drone %s closed connection receiving reply ' \ + 'for RPC %s() (%s)' % ( + self.peer, method.name, e) + self.log.exception(error) + raise + except EncodeError as e: + error = 'ERROR: Failed to serialize %s arg for RPC %s() ' \ + 'to Drone %s (%s)' % ( + type(request).__name__, method.name, self.peer, e) + self.log.exception(error) + raise + except DecodeError as e: + error = 'ERROR: Failed to parse %s response for RPC %s() ' \ + 'from Drone %s (%s)' % ( + type(response).__name__, method.name, self.peer, e) + self.log.exception(error) + raise + except RpcMismatchError as e: + error = 'ERROR: Rpc Mismatch for RPC %s() (%s)' % ( + method.name, e) + self.log.exception(error) + raise + except RpcError as e: + error = 'ERROR: error received for RPC %s() (%s) ' % ( + method.name, e) + self.log.exception(error) + raise + finally: + if error: + print(error) + + + diff --git a/binding/setup.py b/binding/setup.py new file mode 100644 index 0000000..8c81b74 --- /dev/null +++ b/binding/setup.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python +# Copyright (C) 2014 Srivats P. +# +# This file is part of "Ostinato" +# +# This 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 + +import json +import os +import shutil +import sys +from setuptools import Command, setup +from setuptools.command.sdist import sdist as _sdist + +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +def ensure_cwd(): + if os.path.split(os.getcwd())[1] != 'binding': + print('ERROR: This script needs to be run from the binding directory') + print('Current Working Directory is %s' % os.getcwd()) + sys.exit(1) + +class sdist(_sdist): + def run(self): + ensure_cwd() + _sdist.run(self) + +class sdist_clean(Command): + description = 'clean stuff generated by sdist' + user_options = [] + def initialize_options(self): + return + + def finalize_options(self): + return + + def run(self): + ensure_cwd() + shutil.rmtree('dist', ignore_errors = True) + shutil.rmtree('python-ostinato.egg-info', ignore_errors = True) + shutil.rmtree('python_ostinato.egg-info', ignore_errors = True) + +# ------- script starts from here ------- # + +with open(os.path.join(os.path.dirname(__file__), 'pkg_info.json')) as f: + pkg_info = json.load(f) + +setup(name = 'python-ostinato', + version = pkg_info['version'], + author = 'Srivats P', + author_email = 'pstavirs@gmail.com', + license = "GPLv3+", + url = 'http://ostinato.org', + description = 'python-ostinato provides python bindings for the Ostinato network packet/traffic generator and analyzer', + long_description = read('README.txt'), + install_requires = ['protobuf>=2.3.0'], + packages = ['ostinato', 'ostinato.protocols'], + package_dir = {'ostinato': '.'}, + package_data = {'ostinato': ['pkg_info.json', 'LICENSE.txt']}, + platforms = ['Any'], + classifiers = [ + 'Development Status :: 4 - Beta', + 'Programming Language :: Python :: 2.7', + 'Intended Audience :: Telecommunications Industry', + 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', + 'Topic :: Software Development :: Testing :: Traffic Generation', + 'Topic :: System :: Networking'], + cmdclass={ + 'sdist': sdist, + 'sdist_clean': sdist_clean}, + ) + diff --git a/client/about.ui b/client/about.ui new file mode 100644 index 0000000..93354ea --- /dev/null +++ b/client/about.ui @@ -0,0 +1,211 @@ + + About + + + + 0 + 0 + 500 + 327 + + + + + 0 + 0 + + + + About Ostinato + + + + + + 0 + + + + Ostinato + + + + + + + + + 0 + 0 + + + + + + + :/icons/logo.png + + + false + + + Qt::AlignCenter + + + + + + + + + Qt::Vertical + + + + 20 + 21 + + + + + + + + + + + :/icons/name.png + + + Qt::AlignCenter + + + + + + + Version/Revision Placeholder + + + Qt::AlignCenter + + + + + + + Copyright (c) 2007-2014 Srivats P. + + + Qt::AlignCenter + + + + + + + <a href="http://ostinato.org">http://ostinato.org</a><br><a href="http://twitter.com/ostinato">@ostinato</a> + + + Qt::RichText + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + + 20 + 21 + + + + + + + + + + + + Logo (c): Dhiman Sengupta +Icons (c): Mark James (http://www.famfamfam.com/lab/icons/silk/) + + + Qt::AlignCenter + + + + + + + + License + + + + + + <p>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.</p><p>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.</p><p>You should have received a copy of the GNU General Public License along with this program. If not, see <a href="http://www.gnu.org/licenses/">http://www.gnu.org/licenses/</a></p> + + + Qt::RichText + + + Qt::AlignCenter + + + true + + + true + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + About + accept() + + + 353 + 280 + + + 286 + 262 + + + + + diff --git a/client/dumpview.cpp b/client/dumpview.cpp new file mode 100644 index 0000000..fe99e01 --- /dev/null +++ b/client/dumpview.cpp @@ -0,0 +1,408 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "dumpview.h" + +//! \todo Enable Scrollbars + +DumpView::DumpView(QWidget *parent) + : QAbstractItemView(parent) +{ + int w, h; + + // NOTE: Monospaced fonts only !!!!!!!!!!! + setFont(QFont("Courier")); + w = fontMetrics().width('X'); + h = fontMetrics().height(); + + mLineHeight = h; + mCharWidth = w; + + mSelectedRow = mSelectedCol = -1; + + // calculate width for offset column and the whitespace that follows it + // 0010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ........ ........ + mOffsetPaneTopRect = QRect(0, 0, w*4, h); + mDumpPaneTopRect = QRect(mOffsetPaneTopRect.right()+w*3, 0, + w*((8*3-1)+2+(8*3-1)), h); + mAsciiPaneTopRect = QRect(mDumpPaneTopRect.right()+w*3, 0, + w*(8+1+8), h); + qDebug("DumpView::DumpView"); +} + +QModelIndex DumpView::indexAt(const QPoint &/*point*/) const +{ +#if 0 + int x = point.x(); + int row, col; + + if (x > mAsciiPaneTopRect.left()) + { + col = (x - mAsciiPaneTopRect.left()) / mCharWidth; + if (col == 8) // don't select whitespace + goto _exit; + else if (col > 8) // adjust for whitespace + col--; + } + else if (x > mDumpPaneTopRect.left()) + { + col = (x - mDumpPaneTopRect.left()) / (mCharWidth*3); + } + row = point.y()/mLineHeight; + + if ((col < 16) && (row < ((data.size()+16)/16))) + { + selrow = row; + selcol = col; + } + else + goto _exit; + + // last row check col + if ((row == (((data.size()+16)/16) - 1)) && (col >= (data.size() % 16))) + goto _exit; + + qDebug("dumpview::selection(%d, %d)", selrow, selcol); + + offset = selrow * 16 + selcol; +#if 0 + for(int i = 0; i < model()->rowCount(parent); i++) + { + QModelIndex index = model()->index(i, 0, parent); + + if (model()->hasChildren(index)) + indexAtOffset(offset, index); // Non Leaf + else + if ( + dump.append(model()->data(index, Qt::UserRole).toByteArray()); // Leaf + // FIXME: Use RawValueRole instead of UserRole + } +#endif +} + +_exit: + // Clear existing selection + selrow = -1; + +#endif + return QModelIndex(); +} + +void DumpView::scrollTo(const QModelIndex &/*index*/, ScrollHint /*hint*/) +{ + // FIXME: implement scrolling +} + +QRect DumpView::visualRect(const QModelIndex &/*index*/) const +{ + // FIXME: calculate actual rect + return rect(); +} + +//protected: +int DumpView::horizontalOffset() const +{ + return horizontalScrollBar()->value(); +} + +bool DumpView::isIndexHidden(const QModelIndex &/*index*/) const +{ + return false; +} + +QModelIndex DumpView::moveCursor(CursorAction /*cursorAction*/, + Qt::KeyboardModifiers /*modifiers*/) +{ + // FIXME(MED): need to implement movement using cursor + return currentIndex(); +} + +void DumpView::setSelection(const QRect &/*rect*/, + QItemSelectionModel::SelectionFlags flags) +{ + // FIXME(HI): calculate indexes using rect + selectionModel()->select(QModelIndex(), flags); +} + +int DumpView::verticalOffset() const +{ + return verticalScrollBar()->value(); +} + +QRegion DumpView::visualRegionForSelection( + const QItemSelection &/*selection*/) const +{ + // FIXME(HI) + return QRegion(rect()); +} + +//protected slots: +void DumpView::dataChanged(const QModelIndex &/*topLeft*/, + const QModelIndex &/*bottomRight*/) +{ + // FIXME(HI) + update(); +} + +void DumpView::selectionChanged(const QItemSelection &/*selected*/, + const QItemSelection &/*deselected*/) +{ + // FIXME(HI) + update(); +} + +void DumpView::populateDump(QByteArray &dump, int &selOfs, int &selSize, + QModelIndex parent) +{ + // FIXME: Use new enum instead of Qt::UserRole + //! \todo (low): generalize this for any model not just our pkt model + + Q_ASSERT(!parent.isValid()); + + qDebug("!!!! %d $$$$", dump.size()); + + for(int i = 0; i < model()->rowCount(parent); i++) + { + QModelIndex index = model()->index(i, 0, parent); + + Q_ASSERT(index.isValid()); + + // Assumption: protocol data is in bytes (not bits) + qDebug("%d: %d bytes", i, model()->data(index, Qt::UserRole).toByteArray().size()); + dump.append(model()->data(index, Qt::UserRole).toByteArray()); + + } + + if (selectionModel()->selectedIndexes().size()) + { + int j, bits; + QModelIndex index; + + Q_ASSERT(selectionModel()->selectedIndexes().size() == 1); + index = selectionModel()->selectedIndexes().at(0); + + if (index.parent().isValid()) + { + // Field + + // SelOfs = SUM(protocol sizes before selected field's protocol) + + // SUM(field sizes before selected field) + + selOfs = 0; + j = index.parent().row() - 1; + while (j >= 0) + { + selOfs += model()->data(index.parent().sibling(j,0), + Qt::UserRole).toByteArray().size(); + j--; + } + + bits = 0; + j = index.row() - 1; + while (j >= 0) + { + bits += model()->data(index.sibling(j,0), Qt::UserRole+1). + toInt(); + j--; + } + selOfs += bits/8; + selSize = model()->data(index, Qt::UserRole).toByteArray().size(); + } + else + { + // Protocol + selOfs = 0; + j = index.row() - 1; + while (j >= 0) + { + selOfs += model()->data(index.sibling(j,0), Qt::UserRole). + toByteArray().size(); + j--; + } + selSize = model()->data(index, Qt::UserRole).toByteArray().size(); + } + } +} + +// TODO(LOW): rewrite this function - it's a mess! +void DumpView::paintEvent(QPaintEvent* /*event*/) +{ + QStylePainter painter(viewport()); + QRect offsetRect = mOffsetPaneTopRect; + QRect dumpRect = mDumpPaneTopRect; + QRect asciiRect = mAsciiPaneTopRect; + QPalette pal = palette(); + static QByteArray data; + //QByteArray ba; + int selOfs = -1, selSize; + int curSelOfs, curSelSize; + + qDebug("dumpview::paintEvent"); + + // FIXME(LOW): unable to set the self widget's font in constructor + painter.setFont(QFont("Courier")); + + // set a white background + painter.fillRect(rect(), QBrush(QColor(Qt::white))); + + if (model()) + { + data.clear(); + populateDump(data, selOfs, selSize); + } + + // display the offset, dump and ascii panes 8 + 8 bytes on a line + for (int i = 0; i < data.size(); i+=16) + { + QString dumpStr, asciiStr; + + //ba = data.mid(i, 16); + + // display offset + painter.drawItemText(offsetRect, Qt::AlignLeft | Qt::AlignTop, pal, + true, QString("%1").arg(i, 4, 16, QChar('0')), QPalette::WindowText); + // construct the dumpStr and asciiStr + for (int j = i; (j < (i+16)) && (j < data.size()); j++) + { + unsigned char c = data.at(j); + + // extra space after 8 bytes + if (((j+8) % 16) == 0) + { + dumpStr.append(" "); + asciiStr.append(" "); + } + + dumpStr.append(QString("%1").arg((uint)c, 2, 16, QChar('0')). + toUpper()).append(" "); + + if (isPrintable(c)) + asciiStr.append(QChar(c)); + else + asciiStr.append(QChar('.')); + } + + // display dump + painter.drawItemText(dumpRect, Qt::AlignLeft | Qt::AlignTop, pal, + true, dumpStr, QPalette::WindowText); + + // display ascii + painter.drawItemText(asciiRect, Qt::AlignLeft | Qt::AlignTop, pal, + true, asciiStr, QPalette::WindowText); + + // if no selection, skip selection painting + if (selOfs < 0) + goto _next; + + // Check overlap between current row and selection + { + QRect r1(i, 0, qMin(16, data.size()-i), 8); + QRect s1(selOfs, 0, selSize, 8); + if (r1.intersects(s1)) + { + QRect t = r1.intersected(s1); + + curSelOfs = t.x(); + curSelSize = t.width(); + } + else + curSelSize = 0; + + } + + // overpaint selection on current row (if any) + if (curSelSize > 0) + { + QRect r; + QString selectedAsciiStr, selectedDumpStr; + + qDebug("dumpview::paintEvent - Highlighted (%d, %d)", + curSelOfs, curSelSize); + + // construct the dumpStr and asciiStr + for (int k = curSelOfs; (k < (curSelOfs + curSelSize)); k++) + { + unsigned char c = data.at(k); + + // extra space after 8 bytes + if (((k+8) % 16) == 0) + { + // Avoid adding space at the start for fields starting + // at second column 8 byte boundary + if (k!=curSelOfs) + { + selectedDumpStr.append(" "); + selectedAsciiStr.append(" "); + } + } + + selectedDumpStr.append(QString("%1").arg((uint)c, 2, 16, + QChar('0')).toUpper()).append(" "); + + if (isPrintable(c)) + selectedAsciiStr.append(QChar(c)); + else + selectedAsciiStr.append(QChar('.')); + } + + // display dump + r = dumpRect; + if ((curSelOfs - i) < 8) + r.translate(mCharWidth*(curSelOfs-i)*3, 0); + else + r.translate(mCharWidth*((curSelOfs-i)*3+1), 0); + + // adjust width taking care of selection stretching between + // the two 8byte columns + if (( (curSelOfs-i) < 8 ) && ( (curSelOfs-i+curSelSize) > 8 )) + r.setWidth((curSelSize * 3 + 1) * mCharWidth); + else + r.setWidth((curSelSize * 3) * mCharWidth); + + painter.fillRect(r, pal.highlight()); + painter.drawItemText(r, Qt::AlignLeft | Qt::AlignTop, pal, + true, selectedDumpStr, QPalette::HighlightedText); + + // display ascii + r = asciiRect; + if ((curSelOfs - i) < 8) + r.translate(mCharWidth*(curSelOfs-i)*1, 0); + else + r.translate(mCharWidth*((curSelOfs-i)*1+1), 0); + + // adjust width taking care of selection stretching between + // the two 8byte columns + if (( (curSelOfs-i) < 8 ) && ( (curSelOfs-i+curSelSize) > 8 )) + r.setWidth((curSelSize * 1 + 1) * mCharWidth); + else + r.setWidth((curSelSize * 1) * mCharWidth); + + painter.fillRect(r, pal.highlight()); + painter.drawItemText(r, Qt::AlignLeft | Qt::AlignTop, pal, + true, selectedAsciiStr, QPalette::HighlightedText); + } + +_next: + // move the rects down + offsetRect.translate(0, mLineHeight); + dumpRect.translate(0, mLineHeight); + asciiRect.translate(0, mLineHeight); + } +} + diff --git a/client/dumpview.h b/client/dumpview.h new file mode 100644 index 0000000..b170cd0 --- /dev/null +++ b/client/dumpview.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include // FIXME: High + + +class DumpView: public QAbstractItemView +{ +public: + DumpView(QWidget *parent=0); + + QModelIndex indexAt( const QPoint &point ) const; + void scrollTo( const QModelIndex &index, ScrollHint hint = EnsureVisible ); + QRect visualRect( const QModelIndex &index ) const; + +protected: + int horizontalOffset() const; + bool isIndexHidden( const QModelIndex &index ) const; + QModelIndex moveCursor( CursorAction cursorAction, + Qt::KeyboardModifiers modifiers ); + void setSelection( const QRect &rect, QItemSelectionModel::SelectionFlags flags ); + int verticalOffset() const; + QRegion visualRegionForSelection( const QItemSelection &selection ) const; +protected slots: + void dataChanged( const QModelIndex &topLeft, + const QModelIndex &bottomRight ); + void selectionChanged( const QItemSelection &selected, + const QItemSelection &deselected ); + void paintEvent(QPaintEvent *event); + +private: + void populateDump(QByteArray &dump, int &selOfs, int &selSize, + QModelIndex parent = QModelIndex()); + bool inline isPrintable(char c) + {if ((c > 48) && (c < 126)) return true; else return false; } + +private: + QRect mOffsetPaneTopRect; + QRect mDumpPaneTopRect; + QRect mAsciiPaneTopRect; + int mSelectedRow, mSelectedCol; + int mLineHeight; + int mCharWidth; +}; + diff --git a/client/hexlineedit.cpp b/client/hexlineedit.cpp new file mode 100644 index 0000000..6150084 --- /dev/null +++ b/client/hexlineedit.cpp @@ -0,0 +1,91 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "hexlineedit.h" +#include "qdebug.h" + +QString & uintToHexStr(quint64 num, QString &hexStr, quint8 octets); + +HexLineEdit::HexLineEdit( QWidget * parent) + : QLineEdit(parent) +{ + //QLineEdit::QLineEdit(parent); +} + +void HexLineEdit::focusOutEvent(QFocusEvent* /*e*/) +{ +#if 0 + const QValidator *v = validator(); + if ( v ) + { + int curpos = cursorPosition(); + QString str = text(); + if ( v->validate( str, curpos ) == QValidator::Acceptable ) + { + if ( curpos != cursorPosition() ) + setCursorPosition( curpos ); + if ( str != text() ) + setText( str ); + } + else + { + if ( curpos != cursorPosition() ) + setCursorPosition( curpos ); + str = text(); + v->fixup( str ); + if ( str != text() ) + { + setText( str ); + } + } + } + QLineEdit::focusOutEvent( e ); + emit focusOut(); +#else +#define uintToHexStr(num, bytesize) \ + QString("%1").arg((num), (bytesize)*2 , 16, QChar('0')) + + bool isOk; + ulong num; + + qDebug("before = %s\n", text().toAscii().data()); + num = text().remove(QChar(' ')).toULong(&isOk, 16); + setText(uintToHexStr(num, 4)); + qDebug("after = %s\n", text().toAscii().data()); +#undef uintToHexStr +#endif +} + +#if 0 +void HexLineEdit::focusInEvent( QFocusEvent *e ) +{ + QLineEdit::focusInEvent( e ); + emit focusIn(); +} + +void HexLineEdit::keyPressEvent( QKeyEvent *e ) +{ + QLineEdit::keyPressEvent( e ); + if ( e->key() == Key_Enter || e->key() == Key_Return ) + { + setSelection( 0, text().length() ); + } +} +#endif + diff --git a/client/hexlineedit.h b/client/hexlineedit.h new file mode 100644 index 0000000..20ad460 --- /dev/null +++ b/client/hexlineedit.h @@ -0,0 +1,43 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _HEXLINEEDIT +#define _HEXLINEEDIT + +#include + +class HexLineEdit : public QLineEdit +{ + Q_OBJECT +public: + // Constructors + HexLineEdit ( QWidget * parent); + +protected: + void focusOutEvent( QFocusEvent *e ); + //void focusInEvent( QFocusEvent *e ); + //void keyPressEvent( QKeyEvent *e ); + +signals: + //void focusIn(); + void focusOut(); +}; + +#endif + diff --git a/client/icons/about.png b/client/icons/about.png new file mode 100644 index 0000000..95fb35e Binary files /dev/null and b/client/icons/about.png differ diff --git a/client/icons/arrow_down.png b/client/icons/arrow_down.png new file mode 100644 index 0000000..2c4e279 Binary files /dev/null and b/client/icons/arrow_down.png differ diff --git a/client/icons/arrow_left.png b/client/icons/arrow_left.png new file mode 100644 index 0000000..5dc6967 Binary files /dev/null and b/client/icons/arrow_left.png differ diff --git a/client/icons/arrow_right.png b/client/icons/arrow_right.png new file mode 100644 index 0000000..b1a1819 Binary files /dev/null and b/client/icons/arrow_right.png differ diff --git a/client/icons/arrow_up.png b/client/icons/arrow_up.png new file mode 100644 index 0000000..1ebb193 Binary files /dev/null and b/client/icons/arrow_up.png differ diff --git a/client/icons/bullet_error.png b/client/icons/bullet_error.png new file mode 100644 index 0000000..bca2b49 Binary files /dev/null and b/client/icons/bullet_error.png differ diff --git a/client/icons/bullet_green.png b/client/icons/bullet_green.png new file mode 100644 index 0000000..058ad26 Binary files /dev/null and b/client/icons/bullet_green.png differ diff --git a/client/icons/bullet_orange.png b/client/icons/bullet_orange.png new file mode 100644 index 0000000..fa63024 Binary files /dev/null and b/client/icons/bullet_orange.png differ diff --git a/client/icons/bullet_red.png b/client/icons/bullet_red.png new file mode 100644 index 0000000..0cd8031 Binary files /dev/null and b/client/icons/bullet_red.png differ diff --git a/client/icons/bullet_white.png b/client/icons/bullet_white.png new file mode 100644 index 0000000..a9af8d4 Binary files /dev/null and b/client/icons/bullet_white.png differ diff --git a/client/icons/bullet_yellow.png b/client/icons/bullet_yellow.png new file mode 100644 index 0000000..6469cea Binary files /dev/null and b/client/icons/bullet_yellow.png differ diff --git a/client/icons/control_play.png b/client/icons/control_play.png new file mode 100644 index 0000000..0846555 Binary files /dev/null and b/client/icons/control_play.png differ diff --git a/client/icons/control_stop.png b/client/icons/control_stop.png new file mode 100644 index 0000000..893bb60 Binary files /dev/null and b/client/icons/control_stop.png differ diff --git a/client/icons/deco_exclusive.png b/client/icons/deco_exclusive.png new file mode 100644 index 0000000..911da3f Binary files /dev/null and b/client/icons/deco_exclusive.png differ diff --git a/client/icons/delete.png b/client/icons/delete.png new file mode 100644 index 0000000..08f2493 Binary files /dev/null and b/client/icons/delete.png differ diff --git a/client/icons/exit.png b/client/icons/exit.png new file mode 100644 index 0000000..2541d2b Binary files /dev/null and b/client/icons/exit.png differ diff --git a/client/icons/gaps.png b/client/icons/gaps.png new file mode 100644 index 0000000..17d7926 Binary files /dev/null and b/client/icons/gaps.png differ diff --git a/client/icons/logo.icns b/client/icons/logo.icns new file mode 100644 index 0000000..259376a Binary files /dev/null and b/client/icons/logo.icns differ diff --git a/client/icons/logo.ico b/client/icons/logo.ico new file mode 100644 index 0000000..28f1f06 Binary files /dev/null and b/client/icons/logo.ico differ diff --git a/client/icons/logo.png b/client/icons/logo.png new file mode 100644 index 0000000..6a03e6a Binary files /dev/null and b/client/icons/logo.png differ diff --git a/client/icons/magnifier.png b/client/icons/magnifier.png new file mode 100644 index 0000000..cf3d97f Binary files /dev/null and b/client/icons/magnifier.png differ diff --git a/client/icons/name.png b/client/icons/name.png new file mode 100644 index 0000000..b78ddc4 Binary files /dev/null and b/client/icons/name.png differ diff --git a/client/icons/portgroup_add.png b/client/icons/portgroup_add.png new file mode 100644 index 0000000..db604ee Binary files /dev/null and b/client/icons/portgroup_add.png differ diff --git a/client/icons/portgroup_connect.png b/client/icons/portgroup_connect.png new file mode 100644 index 0000000..024138e Binary files /dev/null and b/client/icons/portgroup_connect.png differ diff --git a/client/icons/portgroup_delete.png b/client/icons/portgroup_delete.png new file mode 100644 index 0000000..5e9b268 Binary files /dev/null and b/client/icons/portgroup_delete.png differ diff --git a/client/icons/portgroup_disconnect.png b/client/icons/portgroup_disconnect.png new file mode 100644 index 0000000..b335cb1 Binary files /dev/null and b/client/icons/portgroup_disconnect.png differ diff --git a/client/icons/portstats_clear.png b/client/icons/portstats_clear.png new file mode 100644 index 0000000..48cfa97 Binary files /dev/null and b/client/icons/portstats_clear.png differ diff --git a/client/icons/portstats_clear_all.png b/client/icons/portstats_clear_all.png new file mode 100644 index 0000000..612612b Binary files /dev/null and b/client/icons/portstats_clear_all.png differ diff --git a/client/icons/portstats_filter.png b/client/icons/portstats_filter.png new file mode 100644 index 0000000..4606087 Binary files /dev/null and b/client/icons/portstats_filter.png differ diff --git a/client/icons/preferences.png b/client/icons/preferences.png new file mode 100644 index 0000000..565a933 Binary files /dev/null and b/client/icons/preferences.png differ diff --git a/client/icons/qt.png b/client/icons/qt.png new file mode 100644 index 0000000..48fa9fc Binary files /dev/null and b/client/icons/qt.png differ diff --git a/client/icons/sound_mute.png b/client/icons/sound_mute.png new file mode 100644 index 0000000..b652d2a Binary files /dev/null and b/client/icons/sound_mute.png differ diff --git a/client/icons/sound_none.png b/client/icons/sound_none.png new file mode 100644 index 0000000..b497ebd Binary files /dev/null and b/client/icons/sound_none.png differ diff --git a/client/icons/stream_add.png b/client/icons/stream_add.png new file mode 100644 index 0000000..04f22ba Binary files /dev/null and b/client/icons/stream_add.png differ diff --git a/client/icons/stream_delete.png b/client/icons/stream_delete.png new file mode 100644 index 0000000..8ce71c4 Binary files /dev/null and b/client/icons/stream_delete.png differ diff --git a/client/icons/stream_duplicate.png b/client/icons/stream_duplicate.png new file mode 100644 index 0000000..3262767 Binary files /dev/null and b/client/icons/stream_duplicate.png differ diff --git a/client/icons/stream_edit.png b/client/icons/stream_edit.png new file mode 100644 index 0000000..47b75a4 Binary files /dev/null and b/client/icons/stream_edit.png differ diff --git a/client/main.cpp b/client/main.cpp new file mode 100644 index 0000000..94d3baf --- /dev/null +++ b/client/main.cpp @@ -0,0 +1,94 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "mainwindow.h" +#include "../common/ostprotolib.h" +#include "../common/protocolmanager.h" +#include "../common/protocolwidgetfactory.h" +#include "preferences.h" +#include "settings.h" + +#include +#include +#include + +#include + +extern const char* version; +extern const char* revision; +extern ProtocolManager *OstProtocolManager; +extern ProtocolWidgetFactory *OstProtocolWidgetFactory; + +QSettings *appSettings; +QMainWindow *mainWindow; + +#if defined(Q_OS_WIN32) +QString kGzipPathDefaultValue; +QString kDiffPathDefaultValue; +QString kAwkPathDefaultValue; +#endif + +int main(int argc, char* argv[]) +{ + QApplication app(argc, argv); + int exitCode; + +#if defined(Q_OS_WIN32) + kGzipPathDefaultValue = app.applicationDirPath() + "/gzip.exe"; + kDiffPathDefaultValue = app.applicationDirPath() + "/diff.exe"; + kAwkPathDefaultValue = app.applicationDirPath() + "/gawk.exe"; +#endif + + app.setApplicationName("Ostinato"); + app.setOrganizationName("Ostinato"); + app.setProperty("version", version); + app.setProperty("revision", revision); + + OstProtocolManager = new ProtocolManager(); + OstProtocolWidgetFactory = new ProtocolWidgetFactory(); + + /* (Portable Mode) If we have a .ini file in the same directory as the + executable, we use that instead of the platform specific location + and format for the settings */ + QString portableIni = QCoreApplication::applicationDirPath() + + "/ostinato.ini"; + if (QFile::exists(portableIni)) + appSettings = new QSettings(portableIni, QSettings::IniFormat); + else + appSettings = new QSettings(); + + OstProtoLib::setExternalApplicationPaths( + appSettings->value(kTsharkPathKey, kTsharkPathDefaultValue).toString(), + appSettings->value(kGzipPathKey, kGzipPathDefaultValue).toString(), + appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(), + appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); + + Preferences::initDefaults(); + + mainWindow = new MainWindow; + mainWindow->show(); + exitCode = app.exec(); + + delete mainWindow; + delete appSettings; + delete OstProtocolManager; + google::protobuf::ShutdownProtobufLibrary(); + + return exitCode; +} diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp new file mode 100644 index 0000000..f69a856 --- /dev/null +++ b/client/mainwindow.cpp @@ -0,0 +1,153 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "mainwindow.h" + +#if 0 +#include "dbgthread.h" +#endif + +#include "portgrouplist.h" +#include "portstatswindow.h" +#include "portswindow.h" +#include "preferences.h" +#include "settings.h" +#include "ui_about.h" +#include "updater.h" + +#include +#include + +extern const char* version; +extern const char* revision; + +PortGroupList *pgl; + +MainWindow::MainWindow(QWidget *parent) + : QMainWindow (parent) +{ + QString serverApp = QCoreApplication::applicationDirPath(); + Updater *updater = new Updater(); + +#ifdef Q_OS_MAC + // applicationDirPath() does not return bundle, but executable inside bundle + serverApp.replace("Ostinato.app", "drone.app"); +#endif + +#ifdef Q_OS_WIN32 + serverApp.append("/drone.exe"); +#else + serverApp.append("/drone"); +#endif + + localServer_ = new QProcess(this); + localServer_->setProcessChannelMode(QProcess::ForwardedChannels); + localServer_->start(serverApp, QStringList()); + + pgl = new PortGroupList; + + portsWindow = new PortsWindow(pgl, this); + statsWindow = new PortStatsWindow(pgl, this); + portsDock = new QDockWidget(tr("Ports and Streams"), this); + portsDock->setObjectName("portsDock"); + portsDock->setFeatures( + portsDock->features() & ~QDockWidget::DockWidgetClosable); + statsDock = new QDockWidget(tr("Statistics"), this); + statsDock->setObjectName("statsDock"); + statsDock->setFeatures( + statsDock->features() & ~QDockWidget::DockWidgetClosable); + + setupUi(this); + + menuFile->insertActions(menuFile->actions().at(0), portsWindow->actions()); + + statsDock->setWidget(statsWindow); + addDockWidget(Qt::BottomDockWidgetArea, statsDock); + portsDock->setWidget(portsWindow); + addDockWidget(Qt::TopDockWidgetArea, portsDock); + + QRect geom = appSettings->value(kApplicationWindowGeometryKey).toRect(); + if (!geom.isNull()) + setGeometry(geom); + QByteArray layout = appSettings->value(kApplicationWindowLayout) + .toByteArray(); + if (layout.size()) + restoreState(layout, 0); + + connect(actionFileExit, SIGNAL(triggered()), this, SLOT(close())); + connect(actionAboutQt, SIGNAL(triggered()), qApp, SLOT(aboutQt())); + + connect(updater, SIGNAL(newVersionAvailable(QString)), + this, SLOT(onNewVersion(QString))); + updater->checkForNewVersion(); +#if 0 + { + DbgThread *dbg = new DbgThread(pgl); + dbg->start(); + } +#endif +} + +MainWindow::~MainWindow() +{ +#ifdef Q_OS_WIN32 + //! \todo - find a way to terminate cleanly + localServer_->kill(); +#else + localServer_->terminate(); +#endif + + delete pgl; + + QByteArray layout = saveState(0); + appSettings->setValue(kApplicationWindowLayout, layout); + appSettings->setValue(kApplicationWindowGeometryKey, geometry()); + + localServer_->waitForFinished(); + delete localServer_; +} + +void MainWindow::on_actionPreferences_triggered() +{ + Preferences *preferences = new Preferences(); + + preferences->exec(); + + delete preferences; +} + +void MainWindow::on_actionHelpAbout_triggered() +{ + QDialog *aboutDialog = new QDialog; + + Ui::About about; + about.setupUi(aboutDialog); + about.versionLabel->setText( + QString("Version: %1 Revision: %2").arg(version).arg(revision)); + + aboutDialog->exec(); + + delete aboutDialog; +} + +void MainWindow::onNewVersion(QString newVersion) +{ + statusBar()->showMessage(QString("New Ostinato version %1 available. " + "Visit http://ostinato.org to download").arg(newVersion)); +} diff --git a/client/mainwindow.h b/client/mainwindow.h new file mode 100644 index 0000000..2b68ca5 --- /dev/null +++ b/client/mainwindow.h @@ -0,0 +1,56 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _MAIN_WINDOW_H +#define _MAIN_WINDOW_H + +#include "ui_mainwindow.h" +#include + +class PortsWindow; +class PortStatsWindow; + +class QDockWidget; +class QProcess; + +class MainWindow : public QMainWindow, private Ui::MainWindow +{ + Q_OBJECT + +private: + QProcess *localServer_; + PortsWindow *portsWindow; + PortStatsWindow *statsWindow; + QDockWidget *portsDock; + QDockWidget *statsDock; + +public: + MainWindow(QWidget *parent = 0); + ~MainWindow(); + +public slots: + void on_actionPreferences_triggered(); + void on_actionHelpAbout_triggered(); + +private slots: + void onNewVersion(QString version); +}; + +#endif + diff --git a/client/mainwindow.ui b/client/mainwindow.ui new file mode 100644 index 0000000..333b2db --- /dev/null +++ b/client/mainwindow.ui @@ -0,0 +1,84 @@ + + MainWindow + + + + 0 + 0 + 700 + 550 + + + + Ostinato + + + :/icons/about.png + + + + + + 0 + 0 + 700 + 21 + + + + + File + + + + + + + + Help + + + + + + + + + + + :/icons/exit.png + + + E&xit + + + + + :/icons/about.png + + + &About + + + + + :/icons/preferences.png + + + Preferences + + + + + :/icons/qt.png + + + About Qt + + + + + + + + diff --git a/client/modeltest.cpp b/client/modeltest.cpp new file mode 100644 index 0000000..2598c58 --- /dev/null +++ b/client/modeltest.cpp @@ -0,0 +1,542 @@ +/**************************************************************************** +** +** Copyright (C) 2007 Trolltech ASA. All rights reserved. +** +** This file is part of the Qt Concurrent project on Trolltech Labs. +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.trolltech.com/products/qt/opensource.html +** +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://www.trolltech.com/products/qt/licensing.html or contact the +** sales department at sales@trolltech.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#include + +#include "modeltest.h" + +Q_DECLARE_METATYPE(QModelIndex) + +/*! + Connect to all of the models signals. Whenever anything happens recheck everything. +*/ +ModelTest::ModelTest(QAbstractItemModel *_model, QObject *parent) : QObject(parent), model(_model), fetchingMore(false) +{ + Q_ASSERT(model); + + connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(columnsInserted(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(columnsRemoved(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(dataChanged(const QModelIndex &, const QModelIndex &)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(layoutAboutToBeChanged ()), this, SLOT(runAllTests())); + connect(model, SIGNAL(layoutChanged ()), this, SLOT(runAllTests())); + connect(model, SIGNAL(modelReset ()), this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(runAllTests())); + + // Special checks for inserting/removing + connect(model, SIGNAL(layoutAboutToBeChanged()), + this, SLOT(layoutAboutToBeChanged())); + connect(model, SIGNAL(layoutChanged()), + this, SLOT(layoutChanged())); + + connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex &, int, int)), + this, SLOT(rowsAboutToBeInserted(const QModelIndex &, int, int))); + connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex &, int, int)), + this, SLOT(rowsAboutToBeRemoved(const QModelIndex &, int, int))); + connect(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), + this, SLOT(rowsInserted(const QModelIndex &, int, int))); + connect(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), + this, SLOT(rowsRemoved(const QModelIndex &, int, int))); + + runAllTests(); +} + +void ModelTest::runAllTests() +{ + if (fetchingMore) + return; + nonDestructiveBasicTest(); + rowCount(); + columnCount(); + hasIndex(); + index(); + parent(); + data(); +} + +/*! + nonDestructiveBasicTest tries to call a number of the basic functions (not all) + to make sure the model doesn't outright segfault, testing the functions that makes sense. +*/ +void ModelTest::nonDestructiveBasicTest() +{ + Q_ASSERT(model->buddy(QModelIndex()) == QModelIndex()); + model->canFetchMore(QModelIndex()); + Q_ASSERT(model->columnCount(QModelIndex()) >= 0); + Q_ASSERT(model->data(QModelIndex()) == QVariant()); + fetchingMore = true; + model->fetchMore(QModelIndex()); + fetchingMore = false; + Qt::ItemFlags flags = model->flags(QModelIndex()); + Q_ASSERT(flags == Qt::ItemIsDropEnabled || flags == 0); + model->hasChildren(QModelIndex()); + model->hasIndex(0, 0); + model->headerData(0, Qt::Horizontal); + model->index(0, 0); + Q_ASSERT(model->index(-1, -1) == QModelIndex()); + model->itemData(QModelIndex()); + QVariant cache; + model->match(QModelIndex(), -1, cache); + model->mimeTypes(); + Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); + Q_ASSERT(model->rowCount() >= 0); + QVariant variant; + model->setData(QModelIndex(), variant, -1); + model->setHeaderData(-1, Qt::Horizontal, QVariant()); + model->setHeaderData(0, Qt::Horizontal, QVariant()); + model->setHeaderData(999999, Qt::Horizontal, QVariant()); + QMap roles; + model->sibling(0, 0, QModelIndex()); + model->span(QModelIndex()); + model->supportedDropActions(); +} + +/*! + Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() + + Models that are dynamically populated are not as fully tested here. + */ +void ModelTest::rowCount() +{ + // check top row + QModelIndex topIndex = model->index(0, 0, QModelIndex()); + int rows = model->rowCount(topIndex); + Q_ASSERT(rows >= 0); + if (rows > 0) + Q_ASSERT(model->hasChildren(topIndex) == true); + + QModelIndex secondLevelIndex = model->index(0, 0, topIndex); + if (secondLevelIndex.isValid()) { // not the top level + // check a row count where parent is valid + rows = model->rowCount(secondLevelIndex); + Q_ASSERT(rows >= 0); + if (rows > 0) + Q_ASSERT(model->hasChildren(secondLevelIndex) == true); + } + + // The models rowCount() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() + */ +void ModelTest::columnCount() +{ + // check top row + QModelIndex topIndex = model->index(0, 0, QModelIndex()); + Q_ASSERT(model->columnCount(topIndex) >= 0); + + // check a column count where parent is valid + QModelIndex childIndex = model->index(0, 0, topIndex); + if (childIndex.isValid()) + Q_ASSERT(model->columnCount(childIndex) >= 0); + + // columnCount() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::hasIndex() + */ +void ModelTest::hasIndex() +{ + // Make sure that invalid values returns an invalid index + Q_ASSERT(model->hasIndex(-2, -2) == false); + Q_ASSERT(model->hasIndex(-2, 0) == false); + Q_ASSERT(model->hasIndex(0, -2) == false); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + // check out of bounds + Q_ASSERT(model->hasIndex(rows, columns) == false); + Q_ASSERT(model->hasIndex(rows + 1, columns + 1) == false); + + if (rows > 0) + Q_ASSERT(model->hasIndex(0, 0) == true); + + // hasIndex() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::index() + */ +void ModelTest::index() +{ + // Make sure that invalid values returns an invalid index + Q_ASSERT(model->index(-2, -2) == QModelIndex()); + Q_ASSERT(model->index(-2, 0) == QModelIndex()); + Q_ASSERT(model->index(0, -2) == QModelIndex()); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + if (rows == 0) + return; + + // Catch off by one errors + Q_ASSERT(model->index(rows, columns) == QModelIndex()); + Q_ASSERT(model->index(0, 0).isValid() == true); + + // Make sure that the same index is *always* returned + QModelIndex a = model->index(0, 0); + QModelIndex b = model->index(0, 0); + Q_ASSERT(a == b); + + // index() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::parent() + */ +void ModelTest::parent() +{ + // Make sure the model wont crash and will return an invalid QModelIndex + // when asked for the parent of an invalid index. + Q_ASSERT(model->parent(QModelIndex()) == QModelIndex()); + + if (model->rowCount() == 0) + return; + + // Column 0 | Column 1 | + // QModelIndex() | | + // \- topIndex | topIndex1 | + // \- childIndex | childIndex1 | + + // Common error test #1, make sure that a top level index has a parent + // that is a invalid QModelIndex. + QModelIndex topIndex = model->index(0, 0, QModelIndex()); + Q_ASSERT(model->parent(topIndex) == QModelIndex()); + + // Common error test #2, make sure that a second level index has a parent + // that is the first level index. + if (model->rowCount(topIndex) > 0) { + QModelIndex childIndex = model->index(0, 0, topIndex); + qDebug("topIndex RCI %x %x %llx", topIndex.row(), topIndex.column(), topIndex.internalId()); + qDebug("topIndex I %llx", topIndex.internalId()); + qDebug("childIndex RCI %x %x %llx", childIndex.row(), childIndex.column(), childIndex.internalId()); + Q_ASSERT(model->parent(childIndex) == topIndex); + } + + // Common error test #3, the second column should NOT have the same children + // as the first column in a row. + // Usually the second column shouldn't have children. + QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); + if (model->rowCount(topIndex1) > 0) { + QModelIndex childIndex = model->index(0, 0, topIndex); + QModelIndex childIndex1 = model->index(0, 0, topIndex1); + Q_ASSERT(childIndex != childIndex1); + } + + // Full test, walk n levels deep through the model making sure that all + // parent's children correctly specify their parent. + checkChildren(QModelIndex()); +} + +/*! + Called from the parent() test. + + A model that returns an index of parent X should also return X when asking + for the parent of the index. + + This recursive function does pretty extensive testing on the whole model in an + effort to catch edge cases. + + This function assumes that rowCount(), columnCount() and index() already work. + If they have a bug it will point it out, but the above tests should have already + found the basic bugs because it is easier to figure out the problem in + those tests then this one. + */ +void ModelTest::checkChildren(const QModelIndex &parent, int currentDepth) +{ + // First just try walking back up the tree. + QModelIndex p = parent; + while (p.isValid()) + p = p.parent(); + + // For models that are dynamically populated + if (model->canFetchMore(parent)) { + fetchingMore = true; + model->fetchMore(parent); + fetchingMore = false; + } + + int rows = model->rowCount(parent); + int columns = model->columnCount(parent); + + if (rows > 0) + Q_ASSERT(model->hasChildren(parent)); + + // Some further testing against rows(), columns(), and hasChildren() + Q_ASSERT(rows >= 0); + Q_ASSERT(columns >= 0); + if (rows > 0) + Q_ASSERT(model->hasChildren(parent) == true); + + //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows + // << "columns:" << columns << "parent column:" << parent.column(); + + Q_ASSERT(model->hasIndex(rows + 1, 0, parent) == false); + for (int r = 0; r < rows; ++r) { + if (model->canFetchMore(parent)) { + fetchingMore = true; + model->fetchMore(parent); + fetchingMore = false; + } + Q_ASSERT(model->hasIndex(r, columns + 1, parent) == false); + for (int c = 0; c < columns; ++c) { + Q_ASSERT(model->hasIndex(r, c, parent) == true); + QModelIndex index = model->index(r, c, parent); + // rowCount() and columnCount() said that it existed... + Q_ASSERT(index.isValid() == true); + + // index() should always return the same index when called twice in a row + QModelIndex modifiedIndex = model->index(r, c, parent); + Q_ASSERT(index == modifiedIndex); + + // Make sure we get the same index if we request it twice in a row + QModelIndex a = model->index(r, c, parent); + QModelIndex b = model->index(r, c, parent); + Q_ASSERT(a == b); + + // Some basic checking on the index that is returned + Q_ASSERT(index.model() == model); + Q_ASSERT(index.row() == r); + Q_ASSERT(index.column() == c); + // While you can technically return a QVariant usually this is a sign + // of an bug in data() Disable if this really is ok in your model. + //Q_ASSERT(model->data(index, Qt::DisplayRole).isValid() == true); + + // If the next test fails here is some somewhat useful debug you play with. + /* + if (model->parent(index) != parent) { + qDebug() << r << c << currentDepth << model->data(index).toString() + << model->data(parent).toString(); + qDebug() << index << parent << model->parent(index); + // And a view that you can even use to show the model. + //QTreeView view; + //view.setModel(model); + //view.show(); + }*/ + + // Check that we can get back our real parent. + QModelIndex p = model->parent(index); + //qDebug() << "child:" << index; + //qDebug() << p; + //qDebug() << parent; + Q_ASSERT(model->parent(index) == parent); + + // recursively go down the children + if (model->hasChildren(index) && currentDepth < 10 ) { + //qDebug() << r << c << "has children" << model->rowCount(index); + checkChildren(index, ++currentDepth); + }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ + + // make sure that after testing the children that the index doesn't change. + QModelIndex newerIndex = model->index(r, c, parent); + Q_ASSERT(index == newerIndex); + } + } +} + +/*! + Tests model's implementation of QAbstractItemModel::data() + */ +void ModelTest::data() +{ + // Invalid index should return an invalid qvariant + Q_ASSERT(!model->data(QModelIndex()).isValid()); + + if (model->rowCount() == 0) + return; + + // A valid index should have a valid QVariant data + Q_ASSERT(model->index(0, 0).isValid()); + + // shouldn't be able to set data on an invalid index + Q_ASSERT(model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole) == false); + + // General Purpose roles that should return a QString + QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole); + if (variant.isValid()) { + Q_ASSERT(qVariantCanConvert(variant)); + } + variant = model->data(model->index(0, 0), Qt::StatusTipRole); + if (variant.isValid()) { + Q_ASSERT(qVariantCanConvert(variant)); + } + variant = model->data(model->index(0, 0), Qt::WhatsThisRole); + if (variant.isValid()) { + Q_ASSERT(qVariantCanConvert(variant)); + } + + // General Purpose roles that should return a QSize + variant = model->data(model->index(0, 0), Qt::SizeHintRole); + if (variant.isValid()) { + Q_ASSERT(qVariantCanConvert(variant)); + } + + // General Purpose roles that should return a QFont + QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole); + if (fontVariant.isValid()) { + Q_ASSERT(qVariantCanConvert(fontVariant)); + } + + // Check that the alignment is one we know about + QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); + if (textAlignmentVariant.isValid()) { + int alignment = textAlignmentVariant.toInt(); + Q_ASSERT(alignment == Qt::AlignLeft || + alignment == Qt::AlignRight || + alignment == Qt::AlignHCenter || + alignment == Qt::AlignJustify || + alignment == Qt::AlignTop || + alignment == Qt::AlignBottom || + alignment == Qt::AlignVCenter || + alignment == Qt::AlignCenter || + alignment == Qt::AlignAbsolute || + alignment == Qt::AlignLeading || + alignment == Qt::AlignTrailing); + } + + // General Purpose roles that should return a QColor + QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole); + if (colorVariant.isValid()) { + Q_ASSERT(qVariantCanConvert(colorVariant)); + } + + colorVariant = model->data(model->index(0, 0), Qt::TextColorRole); + if (colorVariant.isValid()) { + Q_ASSERT(qVariantCanConvert(colorVariant)); + } + + // Check that the "check state" is one we know about. + QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); + if (checkStateVariant.isValid()) { + int state = checkStateVariant.toInt(); + Q_ASSERT(state == Qt::Unchecked || + state == Qt::PartiallyChecked || + state == Qt::Checked); + } +} + +/*! + Store what is about to be inserted to make sure it actually happens + + \sa rowsInserted() + */ +void ModelTest::rowsAboutToBeInserted(const QModelIndex &parent, int start, int end) +{ + Q_UNUSED(end); + Changing c; + c.parent = parent; + c.oldSize = model->rowCount(parent); + c.last = model->data(model->index(start - 1, 0, parent)); + c.next = model->data(model->index(start, 0, parent)); + insert.push(c); +} + +/*! + Confirm that what was said was going to happen actually did + + \sa rowsAboutToBeInserted() + */ +void ModelTest::rowsInserted(const QModelIndex & parent, int start, int end) +{ + Changing c = insert.pop(); + Q_ASSERT(c.parent == parent); + Q_ASSERT(c.oldSize + (end - start + 1) == model->rowCount(parent)); + Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); + /* + if (c.next != model->data(model->index(end + 1, 0, c.parent))) { + qDebug() << start << end; + for (int i=0; i < model->rowCount(); ++i) + qDebug() << model->index(i, 0).data().toString(); + qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); + } + */ + Q_ASSERT(c.next == model->data(model->index(end + 1, 0, c.parent))); +} + +void ModelTest::layoutAboutToBeChanged() +{ + for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) + changing.append(QPersistentModelIndex(model->index(i, 0))); +} + +void ModelTest::layoutChanged() +{ + for (int i = 0; i < changing.count(); ++i) { + QPersistentModelIndex p = changing[i]; + Q_ASSERT(p == model->index(p.row(), p.column(), p.parent())); + } + changing.clear(); +} + +/*! + Store what is about to be inserted to make sure it actually happens + + \sa rowsRemoved() + */ +void ModelTest::rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end) +{ + Changing c; + c.parent = parent; + c.oldSize = model->rowCount(parent); + c.last = model->data(model->index(start - 1, 0, parent)); + c.next = model->data(model->index(end + 1, 0, parent)); + remove.push(c); +} + +/*! + Confirm that what was said was going to happen actually did + + \sa rowsAboutToBeRemoved() + */ +void ModelTest::rowsRemoved(const QModelIndex & parent, int start, int end) +{ + Changing c = remove.pop(); + Q_ASSERT(c.parent == parent); + Q_ASSERT(c.oldSize - (end - start + 1) == model->rowCount(parent)); + Q_ASSERT(c.last == model->data(model->index(start - 1, 0, c.parent))); + Q_ASSERT(c.next == model->data(model->index(start, 0, c.parent))); +} + diff --git a/client/modeltest.h b/client/modeltest.h new file mode 100644 index 0000000..38b6b2b --- /dev/null +++ b/client/modeltest.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2007 Trolltech ASA. All rights reserved. +** +** This file is part of the Qt Concurrent project on Trolltech Labs. +** +** This file may be used under the terms of the GNU General Public +** License version 2.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of +** this file. Please review the following information to ensure GNU +** General Public Licensing requirements will be met: +** http://www.trolltech.com/products/qt/opensource.html +** +** If you are unsure which license is appropriate for your use, please +** review the following information: +** http://www.trolltech.com/products/qt/licensing.html or contact the +** sales department at sales@trolltech.com. +** +** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE +** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. +** +****************************************************************************/ + +#ifndef MODELTEST_H +#define MODELTEST_H + +#include +#include +#include + +class ModelTest : public QObject +{ + Q_OBJECT + +public: + ModelTest(QAbstractItemModel *model, QObject *parent = 0); + +private Q_SLOTS: + void nonDestructiveBasicTest(); + void rowCount(); + void columnCount(); + void hasIndex(); + void index(); + void parent(); + void data(); + +protected Q_SLOTS: + void runAllTests(); + void layoutAboutToBeChanged(); + void layoutChanged(); + void rowsAboutToBeInserted(const QModelIndex &parent, int start, int end); + void rowsInserted(const QModelIndex & parent, int start, int end); + void rowsAboutToBeRemoved(const QModelIndex &parent, int start, int end); + void rowsRemoved(const QModelIndex & parent, int start, int end); + +private: + void checkChildren(const QModelIndex &parent, int currentDepth = 0); + + QAbstractItemModel *model; + + struct Changing + { + QModelIndex parent; + int oldSize; + QVariant last; + QVariant next; + }; + QStack insert; + QStack remove; + + bool fetchingMore; + + QList changing; +}; + +#endif diff --git a/client/modeltest.pri b/client/modeltest.pri new file mode 100644 index 0000000..358a077 --- /dev/null +++ b/client/modeltest.pri @@ -0,0 +1,4 @@ +INCLUDEPATH += $$PWD +DEPENDPATH += $$PWD +SOURCES += $$PWD/modeltest.cpp +HEADERS += $$PWD/modeltest.h diff --git a/client/ostinato.pro b/client/ostinato.pro new file mode 100644 index 0000000..456f349 --- /dev/null +++ b/client/ostinato.pro @@ -0,0 +1,95 @@ +TEMPLATE = app +CONFIG += qt ver_info +macx: TARGET = Ostinato +win32:RC_FILE = ostinato.rc +macx:ICON = icons/logo.icns +QT += network script xml +INCLUDEPATH += "../rpc/" "../common/" +win32 { + CONFIG(debug, debug|release) { + LIBS += -L"../common/debug" -lostprotogui -lostproto + LIBS += -L"../rpc/debug" -lpbrpc + POST_TARGETDEPS += \ + "../common/debug/libostprotogui.a" \ + "../common/debug/libostproto.a" \ + "../rpc/debug/libpbrpc.a" + } else { + LIBS += -L"../common/release" -lostprotogui -lostproto + LIBS += -L"../rpc/release" -lpbrpc + POST_TARGETDEPS += \ + "../common/release/libostprotogui.a" \ + "../common/release/libostproto.a" \ + "../rpc/release/libpbrpc.a" + } +} else { + LIBS += -L"../common" -lostprotogui -lostproto + LIBS += -L"../rpc" -lpbrpc + POST_TARGETDEPS += \ + "../common/libostprotogui.a" \ + "../common/libostproto.a" \ + "../rpc/libpbrpc.a" +} +LIBS += -lprotobuf +LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2 +RESOURCES += ostinato.qrc +HEADERS += \ + dumpview.h \ + hexlineedit.h \ + mainwindow.h \ + packetmodel.h \ + port.h \ + portconfigdialog.h \ + portgroup.h \ + portgrouplist.h \ + portmodel.h \ + portstatsmodel.h \ + portstatsfilterdialog.h \ + portstatswindow.h \ + portswindow.h \ + preferences.h \ + settings.h \ + streamconfigdialog.h \ + streamlistdelegate.h \ + streammodel.h \ + updater.h + +FORMS += \ + about.ui \ + mainwindow.ui \ + portconfigdialog.ui \ + portstatsfilter.ui \ + portstatswindow.ui \ + portswindow.ui \ + preferences.ui \ + streamconfigdialog.ui + +SOURCES += \ + dumpview.cpp \ + stream.cpp \ + hexlineedit.cpp \ + main.cpp \ + mainwindow.cpp \ + packetmodel.cpp \ + port.cpp \ + portconfigdialog.cpp \ + portgroup.cpp \ + portgrouplist.cpp \ + portmodel.cpp \ + portstatsmodel.cpp \ + portstatsfilterdialog.cpp \ + portstatswindow.cpp \ + portswindow.cpp \ + preferences.cpp \ + streamconfigdialog.cpp \ + streamlistdelegate.cpp \ + streammodel.cpp \ + updater.cpp + + +QMAKE_DISTCLEAN += object_script.* + +include(../install.pri) +include(../version.pri) + +# TODO(LOW): Test only +CONFIG(debug, debug|release):include(modeltest.pri) diff --git a/client/ostinato.qrc b/client/ostinato.qrc new file mode 100644 index 0000000..d2e81f2 --- /dev/null +++ b/client/ostinato.qrc @@ -0,0 +1,39 @@ + + + icons/about.png + icons/arrow_down.png + icons/arrow_left.png + icons/arrow_right.png + icons/arrow_up.png + icons/bullet_error.png + icons/bullet_green.png + icons/bullet_orange.png + icons/bullet_red.png + icons/bullet_white.png + icons/bullet_yellow.png + icons/control_play.png + icons/control_stop.png + icons/deco_exclusive.png + icons/delete.png + icons/exit.png + icons/gaps.png + icons/logo.png + icons/magnifier.png + icons/name.png + icons/portgroup_add.png + icons/portgroup_connect.png + icons/portgroup_delete.png + icons/portgroup_disconnect.png + icons/portstats_clear.png + icons/portstats_clear_all.png + icons/portstats_filter.png + icons/preferences.png + icons/qt.png + icons/sound_mute.png + icons/sound_none.png + icons/stream_add.png + icons/stream_delete.png + icons/stream_duplicate.png + icons/stream_edit.png + + diff --git a/client/ostinato.rc b/client/ostinato.rc new file mode 100644 index 0000000..41983b2 --- /dev/null +++ b/client/ostinato.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "icons/logo.ico" diff --git a/client/packetmodel.cpp b/client/packetmodel.cpp new file mode 100644 index 0000000..ecb4196 --- /dev/null +++ b/client/packetmodel.cpp @@ -0,0 +1,244 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include + +#include "packetmodel.h" +#include "../common/protocollistiterator.h" +#include "../common/abstractprotocol.h" + +PacketModel::PacketModel(QObject *parent) + : QAbstractItemModel(parent) +{ +} + +void PacketModel::setSelectedProtocols(ProtocolListIterator &iter) +{ + QList currentProtocols; + + iter.toFront(); + while (iter.hasNext()) + currentProtocols.append(iter.next()); + + if (mSelectedProtocols != currentProtocols) + { + mSelectedProtocols = currentProtocols; + reset(); + } + else + { + emit layoutAboutToBeChanged(); + emit layoutChanged(); + } +} + +int PacketModel::rowCount(const QModelIndex &parent) const +{ + IndexId parentId; + + // qDebug("in %s", __FUNCTION__); + + // Parent == Invalid i.e. Invisible Root. + // ==> Children are Protocol (Top Level) Items + if (!parent.isValid()) + return mSelectedProtocols.size(); + + // Parent - Valid Item + parentId.w = parent.internalId(); + switch(parentId.ws.type) + { + case ITYP_PROTOCOL: + return mSelectedProtocols.at(parentId.ws.protocol)->frameFieldCount(); + case ITYP_FIELD: + return 0; + default: + qWarning("%s: Unhandled ItemType", __FUNCTION__); + } + + Q_ASSERT(1 == 0); // Unreachable code + qWarning("%s: Catch all - need to investigate", __FUNCTION__); + return 0; // catch all +} + +int PacketModel::columnCount(const QModelIndex &/*parent*/) const +{ + return 1; +} + +QModelIndex PacketModel::index(int row, int col, const QModelIndex &parent) const +{ + QModelIndex index; + IndexId id, parentId; + + if (!hasIndex(row, col, parent)) + goto _exit; + + // Parent is Invisible Root + // Request for a Protocol Item + if (!parent.isValid()) + { + id.w = 0; + id.ws.type = ITYP_PROTOCOL; + id.ws.protocol = row; + index = createIndex(row, col, id.w); + goto _exit; + } + + // Parent is a Valid Item + parentId.w = parent.internalId(); + id.w = parentId.w; + switch(parentId.ws.type) + { + case ITYP_PROTOCOL: + id.ws.type = ITYP_FIELD; + index = createIndex(row, col, id.w); + goto _exit; + + case ITYP_FIELD: + Q_ASSERT(1 == 0); // Unreachable code + goto _exit; + + default: + qWarning("%s: Unhandled ItemType", __FUNCTION__); + } + + Q_ASSERT(1 == 0); // Unreachable code + +_exit: + return index; +} + +QModelIndex PacketModel::parent(const QModelIndex &index) const +{ + QModelIndex parentIndex; + IndexId id, parentId; + + if (!index.isValid()) + return QModelIndex(); + + id.w = index.internalId(); + parentId.w = id.w; + switch(id.ws.type) + { + case ITYP_PROTOCOL: + // return invalid index for invisible root + goto _exit; + + case ITYP_FIELD: + parentId.ws.type = ITYP_PROTOCOL; + parentIndex = createIndex(id.ws.protocol, 0, parentId.w); + goto _exit; + + default: + qWarning("%s: Unhandled ItemType", __FUNCTION__); + } + + Q_ASSERT(1 == 1); // Unreachable code + +_exit: + return parentIndex; +} + +QVariant PacketModel::data(const QModelIndex &index, int role) const +{ + IndexId id; + int fieldIdx = 0; + + if (!index.isValid()) + return QVariant(); + + id.w = index.internalId(); + + if (id.ws.type == ITYP_FIELD) + { + const AbstractProtocol *p = mSelectedProtocols.at(id.ws.protocol); + int n = index.row() + 1; + + while (n) + { + if (p->fieldFlags(fieldIdx).testFlag(AbstractProtocol::FrameField)) + n--; + fieldIdx++; + } + fieldIdx--; + } + + // FIXME(HI): Relook at this completely + if (role == Qt::UserRole) + { + switch(id.ws.type) + { + case ITYP_PROTOCOL: + qDebug("*** %d/%d", id.ws.protocol, mSelectedProtocols.size()); + return mSelectedProtocols.at(id.ws.protocol)-> + protocolFrameValue(); + + case ITYP_FIELD: + return mSelectedProtocols.at(id.ws.protocol)->fieldData( + fieldIdx, AbstractProtocol::FieldFrameValue); + + default: + qWarning("%s: Unhandled ItemType", __FUNCTION__); + } + return QByteArray(); + } + + // FIXME: Use a new enum here instead of UserRole + if (role == (Qt::UserRole+1)) + { + switch(id.ws.type) + { + case ITYP_PROTOCOL: + return mSelectedProtocols.at(id.ws.protocol)-> + protocolFrameValue().size(); + + case ITYP_FIELD: + return mSelectedProtocols.at(id.ws.protocol)->fieldData( + fieldIdx, AbstractProtocol::FieldBitSize); + + default: + qWarning("%s: Unhandled ItemType", __FUNCTION__); + } + return QVariant(); + } + + if (role != Qt::DisplayRole) + return QVariant(); + + switch(id.ws.type) + { + case ITYP_PROTOCOL: + return QString("%1 (%2)") + .arg(mSelectedProtocols.at(id.ws.protocol)->shortName()) + .arg(mSelectedProtocols.at(id.ws.protocol)->name()); + + case ITYP_FIELD: + return mSelectedProtocols.at(id.ws.protocol)->fieldData(fieldIdx, + AbstractProtocol::FieldName).toString() + QString(" : ") + + mSelectedProtocols.at(id.ws.protocol)->fieldData(fieldIdx, + AbstractProtocol::FieldTextValue).toString(); + + default: + qWarning("%s: Unhandled ItemType", __FUNCTION__); + } + + Q_ASSERT(1 == 1); // Unreachable code + + return QVariant(); +} diff --git a/client/packetmodel.h b/client/packetmodel.h new file mode 100644 index 0000000..08dcea9 --- /dev/null +++ b/client/packetmodel.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PACKET_MODEL_H +#define _PACKET_MODEL_H + +#include + +class ProtocolListIterator; +class AbstractProtocol; + +class PacketModel: public QAbstractItemModel +{ + +public: + PacketModel(QObject *parent = 0); + void setSelectedProtocols(ProtocolListIterator &iter); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int /*section*/, Qt::Orientation /*orientation*/, + int /*role= Qt::DisplayRole*/) const { + return QVariant(); + } + QModelIndex index (int row, int col, const QModelIndex & parent = QModelIndex() ) const; + QModelIndex parent(const QModelIndex &index) const; + +private: + typedef union _IndexId + { + quint32 w; + struct + { + quint16 type; +#define ITYP_PROTOCOL 1 +#define ITYP_FIELD 2 + quint16 protocol; // protocol is valid for both ITYPs + } ws; + } IndexId; + + QList mSelectedProtocols; +}; +#endif + diff --git a/client/port.cpp b/client/port.cpp new file mode 100644 index 0000000..e23f0ac --- /dev/null +++ b/client/port.cpp @@ -0,0 +1,598 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "port.h" + +#include "abstractfileformat.h" + +#include +#include +#include +#include +#include +#include + +extern QMainWindow *mainWindow; + +uint Port::mAllocStreamId = 0; + +static const int kEthOverhead = 20; + +uint Port::newStreamId() +{ + return mAllocStreamId++; +} + +Port::Port(quint32 id, quint32 portGroupId) +{ + mPortId = id; + d.mutable_port_id()->set_id(id); + stats.mutable_port_id()->set_id(id); + mPortGroupId = portGroupId; + capFile_ = NULL; +} + +Port::~Port() +{ + qDebug("%s", __FUNCTION__); + while (!mStreams.isEmpty()) + delete mStreams.takeFirst(); +} + +void Port::updatePortConfig(OstProto::Port *port) +{ + d.MergeFrom(*port); +} + +void Port::updateStreamOrdinalsFromIndex() +{ + for (int i=0; i < mStreams.size(); i++) + mStreams[i]->setOrdinal(i); +} + +void Port::reorderStreamsByOrdinals() +{ + qSort(mStreams.begin(), mStreams.end(), StreamBase::StreamLessThan); +} + +void Port::recalculateAverageRates() +{ + double pps = 0; + double bps = 0; + int n = 0; + + foreach (Stream* s, mStreams) + { + if (!s->isEnabled()) + continue; + + double r = s->averagePacketRate(); + pps += r; + bps += r * (s->frameLenAvg() + kEthOverhead) * 8; + n++; + + if ((transmitMode() == OstProto::kSequentialTransmit) + && (s->nextWhat() == Stream::e_nw_stop)) + break; + } + + if (n) + { + switch (transmitMode()) + { + case OstProto::kSequentialTransmit: + avgPacketsPerSec_ = pps/n; + avgBitsPerSec_ = bps/n; + break; + case OstProto::kInterleavedTransmit: + avgPacketsPerSec_ = pps; + avgBitsPerSec_ = bps; + break; + default: + Q_ASSERT(false); // Unreachable!! + } + numActiveStreams_ = n; + } + else + avgPacketsPerSec_ = avgBitsPerSec_ = numActiveStreams_ = 0; + + qDebug("%s: avgPps = %g avgBps = %g numActive = %d", __FUNCTION__, + avgPacketsPerSec_, avgBitsPerSec_, numActiveStreams_); + + emit portRateChanged(mPortGroupId, mPortId); + +} + +void Port::setAveragePacketRate(double packetsPerSec) +{ + double rate = 0; + double pps = 0; + double bps = 0; + int n = 0; + + qDebug("@%s: packetsPerSec = %g", __FUNCTION__, packetsPerSec); + qDebug("@%s: avgPps = %g avgBps = %g numActive = %d", __FUNCTION__, + avgPacketsPerSec_, avgBitsPerSec_, numActiveStreams_); + foreach (Stream* s, mStreams) + { + if (!s->isEnabled()) + continue; + + switch (transmitMode()) + { + case OstProto::kSequentialTransmit: + rate = s->averagePacketRate() * (packetsPerSec/avgPacketsPerSec_); + break; + case OstProto::kInterleavedTransmit: + rate = s->averagePacketRate() + + ((s->averagePacketRate()/avgPacketsPerSec_) * + (packetsPerSec - avgPacketsPerSec_)); + break; + default: + Q_ASSERT(false); // Unreachable!! + } + + qDebug("cur stream pps = %g", s->averagePacketRate()); + + s->setAveragePacketRate(rate); + + qDebug("new stream pps = %g", s->averagePacketRate()); + + double r = s->averagePacketRate(); + pps += r; + bps += r * (s->frameLenAvg() + kEthOverhead) * 8; + n++; + + if ((transmitMode() == OstProto::kSequentialTransmit) + && (s->nextWhat() == Stream::e_nw_stop)) + break; + } + + if (n) + { + switch (transmitMode()) + { + case OstProto::kSequentialTransmit: + avgPacketsPerSec_ = pps/n; + avgBitsPerSec_ = bps/n; + break; + case OstProto::kInterleavedTransmit: + avgPacketsPerSec_ = pps; + avgBitsPerSec_ = bps; + break; + default: + Q_ASSERT(false); // Unreachable!! + } + numActiveStreams_ = n; + } + else + avgPacketsPerSec_ = avgBitsPerSec_ = numActiveStreams_ = 0; + + qDebug("%s: avgPps = %g avgBps = %g numActive = %d", __FUNCTION__, + avgPacketsPerSec_, avgBitsPerSec_, numActiveStreams_); + + emit portRateChanged(mPortGroupId, mPortId); +} + +void Port::setAverageBitRate(double bitsPerSec) +{ + double rate = 0; + double pps = 0; + double bps = 0; + int n = 0; + + qDebug("@%s: bitsPerSec = %g", __FUNCTION__, bitsPerSec); + qDebug("@%s: avgPps = %g avgBps = %g numActive = %d", __FUNCTION__, + avgPacketsPerSec_, avgBitsPerSec_, numActiveStreams_); + foreach (Stream* s, mStreams) + { + if (!s->isEnabled()) + continue; + + switch (transmitMode()) + { + case OstProto::kSequentialTransmit: + rate = s->averagePacketRate() * (bitsPerSec/avgBitsPerSec_); + qDebug("rate = %g", rate); + break; + case OstProto::kInterleavedTransmit: + rate = s->averagePacketRate() + + ((s->averagePacketRate()/avgPacketsPerSec_) + * ((bitsPerSec - avgBitsPerSec_) + / ((s->frameLenAvg()+kEthOverhead)*8))); + break; + default: + Q_ASSERT(false); // Unreachable!! + } + + qDebug("cur stream pps = %g", s->averagePacketRate()); + + s->setAveragePacketRate(rate); + + qDebug("new stream pps = %g", s->averagePacketRate()); + + double r = s->averagePacketRate(); + pps += r; + bps += r * (s->frameLenAvg() + kEthOverhead) * 8; + n++; + + if ((transmitMode() == OstProto::kSequentialTransmit) + && (s->nextWhat() == Stream::e_nw_stop)) + break; + } + + if (n) + { + switch (transmitMode()) + { + case OstProto::kSequentialTransmit: + avgPacketsPerSec_ = pps/n; + avgBitsPerSec_ = bps/n; + break; + case OstProto::kInterleavedTransmit: + avgPacketsPerSec_ = pps; + avgBitsPerSec_ = bps; + break; + default: + Q_ASSERT(false); // Unreachable!! + } + numActiveStreams_ = n; + } + else + avgPacketsPerSec_ = avgBitsPerSec_ = numActiveStreams_ = 0; + + qDebug("%s: avgPps = %g avgBps = %g numActive = %d", __FUNCTION__, + avgPacketsPerSec_, avgBitsPerSec_, numActiveStreams_); + emit portRateChanged(mPortGroupId, mPortId); +} + +bool Port::newStreamAt(int index, OstProto::Stream const *stream) +{ + Stream *s = new Stream; + + if (index > mStreams.size()) + return false; + + if (stream) + s->protoDataCopyFrom(*stream); + + s->setId(newStreamId()); + mStreams.insert(index, s); + updateStreamOrdinalsFromIndex(); + recalculateAverageRates(); + + return true; +} + +bool Port::deleteStreamAt(int index) +{ + if (index >= mStreams.size()) + return false; + + delete mStreams.takeAt(index); + updateStreamOrdinalsFromIndex(); + recalculateAverageRates(); + + return true; +} + +bool Port::insertStream(uint streamId) +{ + Stream *s = new Stream; + + s->setId(streamId); + + // FIXME(MED): If a stream with id already exists, what do we do? + mStreams.append(s); + + // Update mAllocStreamId to take into account the stream id received + // from server + if (mAllocStreamId <= streamId) + mAllocStreamId = streamId + 1; + + return true; +} + +bool Port::updateStream(uint streamId, OstProto::Stream *stream) +{ + int i, streamIndex; + + for (i = 0; i < mStreams.size(); i++) + { + if (streamId == mStreams[i]->id()) + goto _found; + } + + qDebug("%s: Invalid stream id %d", __FUNCTION__, streamId); + return false; + +_found: + streamIndex = i; + + mStreams[streamIndex]->protoDataCopyFrom(*stream); + reorderStreamsByOrdinals(); + + return true; +} + +void Port::getDeletedStreamsSinceLastSync( + OstProto::StreamIdList &streamIdList) +{ + streamIdList.clear_stream_id(); + for (int i = 0; i < mLastSyncStreamList.size(); i++) + { + int j; + + for (j = 0; j < mStreams.size(); j++) + { + if (mLastSyncStreamList[i] == mStreams[j]->id()) + break; + } + + if (j < mStreams.size()) + { + // stream still exists! + continue; + } + else + { + // stream has been deleted since last sync + OstProto::StreamId *s; + + s = streamIdList.add_stream_id(); + s->set_id(mLastSyncStreamList.at(i)); + } + } +} + +void Port::getNewStreamsSinceLastSync( + OstProto::StreamIdList &streamIdList) +{ + streamIdList.clear_stream_id(); + for (int i = 0; i < mStreams.size(); i++) + { + if (mLastSyncStreamList.contains(mStreams[i]->id())) + { + // existing stream! + continue; + } + else + { + // new stream! + OstProto::StreamId *s; + + s = streamIdList.add_stream_id(); + s->set_id(mStreams[i]->id()); + } + } +} + +void Port::getModifiedStreamsSinceLastSync( + OstProto::StreamConfigList &streamConfigList) +{ + qDebug("In %s", __FUNCTION__); + + //streamConfigList.mutable_port_id()->set_id(mPortId); + for (int i = 0; i < mStreams.size(); i++) + { + OstProto::Stream *s; + + s = streamConfigList.add_stream(); + mStreams[i]->protoDataCopyInto(*s); + } + qDebug("Done %s", __FUNCTION__); +} + +void Port::when_syncComplete() +{ + //reorderStreamsByOrdinals(); + + mLastSyncStreamList.clear(); + for (int i=0; iid()); +} + +void Port::updateStats(OstProto::PortStats *portStats) +{ + OstProto::PortState oldState; + + oldState = stats.state(); + stats.MergeFrom(*portStats); + + if (oldState.link_state() != stats.state().link_state()) + { + qDebug("portstate changed"); + emit portDataChanged(mPortGroupId, mPortId); + } +} + +void Port::duplicateStreams(const QList &list, int count) +{ + QList sources; + foreach(int index, list) { + OstProto::Stream stream; + Q_ASSERT(index < mStreams.size()); + mStreams.at(index)->protoDataCopyInto(stream); + sources.append(stream); + } + + int insertAt = mStreams.size(); + for (int i=0; i < count; i++) { + for (int j=0; j < sources.size(); j++) { + newStreamAt(insertAt, &sources.at(j)); + // Rename stream by appending the copy count + mStreams.at(insertAt)->setName(QString("%1 (%2)") + .arg(mStreams.at(insertAt)->name()) + .arg(i+1)); + insertAt++; + } + } + + emit streamListChanged(mPortGroupId, mPortId); +} + +bool Port::openStreams(QString fileName, bool append, QString &error) +{ + bool ret = false; + QDialog *optDialog; + QProgressDialog progress("Opening Streams", "Cancel", 0, 0, mainWindow); + OstProto::StreamConfigList streams; + AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromFile(fileName); + + if (fmt == NULL) + goto _fail; + + if ((optDialog = fmt->openOptionsDialog())) + { + int ret; + optDialog->setParent(mainWindow, Qt::Dialog); + ret = optDialog->exec(); + optDialog->setParent(0, Qt::Dialog); + if (ret == QDialog::Rejected) + goto _user_opt_cancel; + } + + progress.setAutoReset(false); + progress.setAutoClose(false); + progress.setMinimumDuration(0); + progress.show(); + + mainWindow->setDisabled(true); + progress.setEnabled(true); // to override the mainWindow disable + + connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString))); + connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int))); + connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int))); + connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel())); + + fmt->openStreamsOffline(fileName, streams, error); + qDebug("after open offline"); + + while (!fmt->isFinished()) + qApp->processEvents(); + qDebug("wait over for offline operation"); + + if (!fmt->result()) + goto _fail; + + // process any remaining events posted from the thread + for (int i = 0; i < 10; i++) + qApp->processEvents(); + + if (!append) + { + int n = numStreams(); + + progress.setLabelText("Deleting existing streams..."); + progress.setRange(0, n); + for (int i = 0; i < n; i++) + { + if (progress.wasCanceled()) + goto _user_cancel; + deleteStreamAt(0); + progress.setValue(i); + if (i % 32 == 0) + qApp->processEvents(); + } + } + + progress.setLabelText("Constructing new streams..."); + progress.setRange(0, streams.stream_size()); + for (int i = 0; i < streams.stream_size(); i++) + { + if (progress.wasCanceled()) + goto _user_cancel; + newStreamAt(mStreams.size(), &streams.stream(i)); + progress.setValue(i); + if (i % 32 == 0) + qApp->processEvents(); + } + +_user_cancel: + emit streamListChanged(mPortGroupId, mPortId); +_user_opt_cancel: + ret = true; + +_fail: + progress.close(); + mainWindow->setEnabled(true); + recalculateAverageRates(); + return ret; +} + +bool Port::saveStreams(QString fileName, QString fileType, QString &error) +{ + bool ret = false; + QProgressDialog progress("Saving Streams", "Cancel", 0, 0, mainWindow); + AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromType(fileType); + OstProto::StreamConfigList streams; + + if (fmt == NULL) + goto _fail; + + progress.setAutoReset(false); + progress.setAutoClose(false); + progress.setMinimumDuration(0); + progress.show(); + + mainWindow->setDisabled(true); + progress.setEnabled(true); // to override the mainWindow disable + + connect(fmt, SIGNAL(status(QString)),&progress,SLOT(setLabelText(QString))); + connect(fmt, SIGNAL(target(int)), &progress, SLOT(setMaximum(int))); + connect(fmt, SIGNAL(progress(int)), &progress, SLOT(setValue(int))); + connect(&progress, SIGNAL(canceled()), fmt, SLOT(cancel())); + + progress.setLabelText("Preparing Streams..."); + progress.setRange(0, mStreams.size()); + streams.mutable_port_id()->set_id(0); + for (int i = 0; i < mStreams.size(); i++) + { + OstProto::Stream *s = streams.add_stream(); + mStreams[i]->protoDataCopyInto(*s); + + if (progress.wasCanceled()) + goto _user_cancel; + progress.setValue(i); + if (i % 32 == 0) + qApp->processEvents(); + } + + fmt->saveStreamsOffline(streams, fileName, error); + qDebug("after save offline"); + + while (!fmt->isFinished()) + qApp->processEvents(); + qDebug("wait over for offline operation"); + + ret = fmt->result(); + goto _exit; + +_user_cancel: + goto _exit; + +_fail: + error = QString("Unsupported File Type - %1").arg(fileType); + goto _exit; + +_exit: + progress.close(); + mainWindow->setEnabled(true); + return ret; +} diff --git a/client/port.h b/client/port.h new file mode 100644 index 0000000..006ba7e --- /dev/null +++ b/client/port.h @@ -0,0 +1,155 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORT_H +#define _PORT_H + +#include +#include +#include +#include + +#include "stream.h" + +//class StreamModel; + +class Port : public QObject { + + Q_OBJECT + + static uint mAllocStreamId; + + OstProto::Port d; + OstProto::PortStats stats; + QTemporaryFile *capFile_; + + // FIXME(HI): consider removing mPortId as it is duplicated inside 'd' + quint32 mPortId; + quint32 mPortGroupId; + QString mUserAlias; // user defined + + double avgPacketsPerSec_; + double avgBitsPerSec_; + int numActiveStreams_; + + QList mLastSyncStreamList; + QList mStreams; // sorted by stream's ordinal value + + uint newStreamId(); + void updateStreamOrdinalsFromIndex(); + void reorderStreamsByOrdinals(); + + +public: + enum AdminStatus { AdminDisable, AdminEnable }; + + // FIXME(HIGH): default args is a hack for QList operations on Port + Port(quint32 id = 0xFFFFFFFF, quint32 pgId = 0xFFFFFFFF); + ~Port(); + + quint32 portGroupId() const { return mPortGroupId; } + const QString& userAlias() const { return mUserAlias; } + + quint32 id() const + { return d.port_id().id(); } + const QString name() const + { return QString().fromStdString(d.name()); } + const QString description() const + { return QString().fromStdString(d.description()); } + const QString notes() const + { return QString().fromStdString(d.notes()); } + const QString userName() const + { return QString().fromStdString(d.user_name()); } + AdminStatus adminStatus() + { return (d.is_enabled()?AdminEnable:AdminDisable); } + bool hasExclusiveControl() + { return d.is_exclusive_control(); } + OstProto::TransmitMode transmitMode() + { return d.transmit_mode(); } + double averagePacketRate() + { return avgPacketsPerSec_; } + double averageBitRate() + { return avgBitsPerSec_; } + + //void setAdminEnable(AdminStatus status) { mAdminStatus = status; } + void setAlias(QString &alias) { mUserAlias = alias; } + //void setExclusive(bool flag); + + int numStreams() { return mStreams.size(); } + Stream* streamByIndex(int index) + { + Q_ASSERT(index < mStreams.size()); + return mStreams[index]; + } + OstProto::LinkState linkState() + { return stats.state().link_state(); } + + OstProto::PortStats getStats() { return stats; } + QTemporaryFile* getCaptureFile() + { + delete capFile_; + capFile_ = new QTemporaryFile(QString(QDir::tempPath()) + .append("/") + .append(name()) + .append(".XXXXXX")); + return capFile_; + } + + // FIXME(MED): naming inconsistency - PortConfig/Stream; also retVal + void updatePortConfig(OstProto::Port *port); + + //! Used by StreamModel + //@{ + bool newStreamAt(int index, OstProto::Stream const *stream = NULL); + bool deleteStreamAt(int index); + //@} + + //! Used by MyService::Stub to update from config received from server + //@{ + bool insertStream(uint streamId); + bool updateStream(uint streamId, OstProto::Stream *stream); + //@} + + void getDeletedStreamsSinceLastSync(OstProto::StreamIdList &streamIdList); + void getNewStreamsSinceLastSync(OstProto::StreamIdList &streamIdList); + void getModifiedStreamsSinceLastSync( + OstProto::StreamConfigList &streamConfigList); + + void when_syncComplete(); + + void setAveragePacketRate(double packetsPerSec); + void setAverageBitRate(double bitsPerSec); + // FIXME(MED): Bad Hack! port should not need an external trigger to + // recalculate - refactor client side domain objects and model objects + void recalculateAverageRates(); + void updateStats(OstProto::PortStats *portStats); + + void duplicateStreams(const QList &list, int count); + + bool openStreams(QString fileName, bool append, QString &error); + bool saveStreams(QString fileName, QString fileType, QString &error); + +signals: + void portRateChanged(int portGroupId, int portId); + void portDataChanged(int portGroupId, int portId); + void streamListChanged(int portGroupId, int portId); + +}; + +#endif diff --git a/client/portconfigdialog.cpp b/client/portconfigdialog.cpp new file mode 100644 index 0000000..ab7f1e8 --- /dev/null +++ b/client/portconfigdialog.cpp @@ -0,0 +1,99 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "portconfigdialog.h" +#include "settings.h" + +PortConfigDialog::PortConfigDialog(OstProto::Port &portConfig, QWidget *parent) + : QDialog(parent), portConfig_(portConfig) +{ + QString currentUser(portConfig_.user_name().c_str()); + + qDebug("In %s", __FUNCTION__); + + setupUi(this); + + switch(portConfig_.transmit_mode()) + { + case OstProto::kSequentialTransmit: + sequentialStreamsButton->setChecked(true); + break; + case OstProto::kInterleavedTransmit: + interleavedStreamsButton->setChecked(true); + break; + default: + Q_ASSERT(false); // Unreachable!!! + break; + } + + // Port Reservation + myself_ = appSettings->value(kUserKey, kUserDefaultValue).toString(); + // XXX: what if myself_ is empty? + if (currentUser.isEmpty()) { + reservedBy_ = kNone; + reservedBy->setText("Unreserved"); + reserveButton->setText("Reserve"); + } + else if (currentUser == myself_) { + reservedBy_ = kSelf; + reservedBy->setText("Reserved by: me ("+currentUser+")"); + reserveButton->setText("Reserve (uncheck to unreserve)"); + reserveButton->setChecked(true); + } + else { + reservedBy_ = kOther; + reservedBy->setText("Reserved by: "+currentUser+""); + reserveButton->setText("Force reserve"); + } + qDebug("reservedBy_ = %d", reservedBy_); + + exclusiveControlButton->setChecked(portConfig_.is_exclusive_control()); +} + +void PortConfigDialog::accept() +{ + if (sequentialStreamsButton->isChecked()) + portConfig_.set_transmit_mode(OstProto::kSequentialTransmit); + else if (interleavedStreamsButton->isChecked()) + portConfig_.set_transmit_mode(OstProto::kInterleavedTransmit); + else + Q_ASSERT(false); // Unreachable!!! + + switch (reservedBy_) { + case kSelf: + if (!reserveButton->isChecked()) + portConfig_.set_user_name(""); // unreserve + break; + + case kOther: + case kNone: + if (reserveButton->isChecked()) + portConfig_.set_user_name( + myself_.toStdString()); // (force) reserve + break; + + default: + qWarning("Unreachable code"); + break; + } + + portConfig_.set_is_exclusive_control(exclusiveControlButton->isChecked()); + + QDialog::accept(); +} diff --git a/client/portconfigdialog.h b/client/portconfigdialog.h new file mode 100644 index 0000000..23b29d7 --- /dev/null +++ b/client/portconfigdialog.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORT_CONFIG_DIALOG_H +#define _PORT_CONFIG_DIALOG_H + +#include "ui_portconfigdialog.h" +#include "protocol.pb.h" +#include + +class PortConfigDialog : public QDialog, public Ui::PortConfigDialog +{ +public: + PortConfigDialog(OstProto::Port &portConfig, QWidget *parent); + +private: + virtual void accept(); + + OstProto::Port &portConfig_; + enum { kNone, kSelf, kOther } reservedBy_; + QString myself_; +}; + +#endif + diff --git a/client/portconfigdialog.ui b/client/portconfigdialog.ui new file mode 100644 index 0000000..1bdc143 --- /dev/null +++ b/client/portconfigdialog.ui @@ -0,0 +1,132 @@ + + PortConfigDialog + + + + 0 + 0 + 244 + 233 + + + + Port Config + + + + + + Transmit Mode + + + + + + Sequential Streams + + + true + + + + + + + Interleaved Streams + + + + + + + + + + Reservation + + + + + + Reserved by: + + + + + + + Reserve + + + + + + + + + + Exclusive Control + + + + + + + Qt::Vertical + + + + 226 + 71 + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + PortConfigDialog + accept() + + + 234 + 205 + + + 157 + 214 + + + + + buttonBox + rejected() + PortConfigDialog + reject() + + + 234 + 205 + + + 243 + 214 + + + + + diff --git a/client/portgroup.cpp b/client/portgroup.cpp new file mode 100644 index 0000000..8e20735 --- /dev/null +++ b/client/portgroup.cpp @@ -0,0 +1,896 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "portgroup.h" + +#include "settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using ::google::protobuf::NewCallback; + +extern QMainWindow *mainWindow; +extern char *version; + +quint32 PortGroup::mPortGroupAllocId = 0; + +PortGroup::PortGroup(QHostAddress ip, quint16 port) +{ + // Allocate an id for self + mPortGroupId = PortGroup::mPortGroupAllocId++; + + portIdList_ = new OstProto::PortIdList; + portStatsList_ = new OstProto::PortStatsList; + + statsController = new PbRpcController(portIdList_, portStatsList_); + isGetStatsPending_ = false; + + compat = kUnknown; + + reconnect = false; + reconnectAfter = kMinReconnectWaitTime; + reconnectTimer = new QTimer(this); + reconnectTimer->setSingleShot(true); + connect(reconnectTimer, SIGNAL(timeout()), + this, SLOT(on_reconnectTimer_timeout())); + + rpcChannel = new PbRpcChannel(ip, port); + serviceStub = new OstProto::OstService::Stub(rpcChannel); + + // FIXME(LOW):Can't for my life figure out why this ain't working! + //QMetaObject::connectSlotsByName(this); + connect(rpcChannel, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(on_rpcChannel_stateChanged(QAbstractSocket::SocketState))); + connect(rpcChannel, SIGNAL(connected()), + this, SLOT(on_rpcChannel_connected())); + connect(rpcChannel, SIGNAL(disconnected()), + this, SLOT(on_rpcChannel_disconnected())); + connect(rpcChannel, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(on_rpcChannel_error(QAbstractSocket::SocketError))); + + connect(this, SIGNAL(portListChanged(quint32)), + this, SLOT(when_portListChanged(quint32)), Qt::QueuedConnection); +} + +PortGroup::~PortGroup() +{ + qDebug("PortGroup Destructor"); + // Disconnect and free rpc channel etc. + PortGroup::disconnectFromHost(); + delete serviceStub; + delete rpcChannel; + delete statsController; +} + + +// ------------------------------------------------ +// Slots +// ------------------------------------------------ +void PortGroup::on_reconnectTimer_timeout() +{ + reconnectAfter *= 2; + if (reconnectAfter > kMaxReconnectWaitTime) + reconnectAfter = kMaxReconnectWaitTime; + + connectToHost(); +} + +void PortGroup::on_rpcChannel_stateChanged(QAbstractSocket::SocketState state) +{ + qDebug("state changed %d", state); + + switch (state) + { + case QAbstractSocket::UnconnectedState: + case QAbstractSocket::ClosingState: + break; + + default: + emit portGroupDataChanged(mPortGroupId); + } +} + +void PortGroup::on_rpcChannel_connected() +{ + OstProto::VersionInfo *verInfo = new OstProto::VersionInfo; + OstProto::VersionCompatibility *verCompat = + new OstProto::VersionCompatibility; + + qDebug("connected\n"); + emit portGroupDataChanged(mPortGroupId); + + reconnectAfter = kMinReconnectWaitTime; + + qDebug("requesting version check ..."); + verInfo->set_version(version); + + PbRpcController *controller = new PbRpcController(verInfo, verCompat); + serviceStub->checkVersion(controller, verInfo, verCompat, + NewCallback(this, &PortGroup::processVersionCompatibility, + controller)); +} + +void PortGroup::processVersionCompatibility(PbRpcController *controller) +{ + OstProto::VersionCompatibility *verCompat + = static_cast(controller->response()); + + Q_ASSERT(verCompat != NULL); + + qDebug("got version result ..."); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _error_exit; + } + + if (verCompat->result() == OstProto::VersionCompatibility::kIncompatible) { + qWarning("incompatible version %s (%s)", version, + qPrintable(QString::fromStdString(verCompat->notes()))); + compat = kIncompatible; + emit portGroupDataChanged(mPortGroupId); + goto _error_exit; + } + + compat = kCompatible; + + { + OstProto::Void *void_ = new OstProto::Void; + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + + qDebug("requesting portlist ..."); + PbRpcController *controller = new PbRpcController(void_, portIdList); + serviceStub->getPortIdList(controller, void_, portIdList, + NewCallback(this, &PortGroup::processPortIdList, controller)); + } + +_error_exit: + delete controller; +} + +void PortGroup::on_rpcChannel_disconnected() +{ + qDebug("disconnected\n"); + emit portListAboutToBeChanged(mPortGroupId); + + while (!mPorts.isEmpty()) + delete mPorts.takeFirst(); + + emit portListChanged(mPortGroupId); + emit portGroupDataChanged(mPortGroupId); + + isGetStatsPending_ = false; + + if (reconnect) + { + qDebug("starting reconnect timer for %d ms ...", reconnectAfter); + reconnectTimer->start(reconnectAfter); + } +} + +void PortGroup::on_rpcChannel_error(QAbstractSocket::SocketError socketError) +{ + qDebug("%s: error %d", __FUNCTION__, socketError); + emit portGroupDataChanged(mPortGroupId); + + if (socketError == QAbstractSocket::RemoteHostClosedError) + reconnect = false; + + qDebug("%s: state %d", __FUNCTION__, rpcChannel->state()); + if ((rpcChannel->state() == QAbstractSocket::UnconnectedState) && reconnect) + { + qDebug("starting reconnect timer for %d ms...", reconnectAfter); + reconnectTimer->start(reconnectAfter); + } +} + +void PortGroup::when_portListChanged(quint32 /*portGroupId*/) +{ + if (state() == QAbstractSocket::ConnectedState && numPorts() <= 0) + { + QMessageBox::warning(NULL, tr("No ports in portgroup"), + QString("The portgroup %1:%2 does not contain any ports!\n\n" + "Packet Transmit/Capture requires elevated privileges. " + "Please ensure that you are running 'drone' - the server " + "component of Ostinato with admin/root OR setuid privilege.\n\n" + "For more information see " + "http://code.google.com/p/ostinato/wiki/FAQ#" + "Q._Port_group_has_no_interfaces") + .arg(serverAddress().toString()) + .arg(int(serverPort()))); + } +} + +void PortGroup::processPortIdList(PbRpcController *controller) +{ + OstProto::PortIdList *portIdList + = static_cast(controller->response()); + + Q_ASSERT(portIdList != NULL); + + qDebug("got a portlist ..."); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _error_exit; + } + + emit portListAboutToBeChanged(mPortGroupId); + + for(int i = 0; i < portIdList->port_id_size(); i++) + { + Port *p; + + p = new Port(portIdList->port_id(i).id(), mPortGroupId); + connect(p, SIGNAL(portDataChanged(int, int)), + this, SIGNAL(portGroupDataChanged(int, int))); + qDebug("before port append\n"); + mPorts.append(p); + } + + emit portListChanged(mPortGroupId); + + portIdList_->CopyFrom(*portIdList); + + // Request PortConfigList + { + qDebug("requesting port config list ..."); + OstProto::PortIdList *portIdList2 = new OstProto::PortIdList(); + OstProto::PortConfigList *portConfigList = new OstProto::PortConfigList(); + PbRpcController *controller2 = new PbRpcController(portIdList2, + portConfigList); + + portIdList2->CopyFrom(*portIdList); + + serviceStub->getPortConfig(controller, portIdList2, portConfigList, + NewCallback(this, &PortGroup::processPortConfigList, controller2)); + + goto _exit; + } + +_error_exit: +_exit: + delete controller; +} + +void PortGroup::processPortConfigList(PbRpcController *controller) +{ + OstProto::PortConfigList *portConfigList + = static_cast(controller->response()); + + qDebug("In %s", __FUNCTION__); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _error_exit; + } + + //emit portListAboutToBeChanged(mPortGroupId); + + for(int i = 0; i < portConfigList->port_size(); i++) + { + uint id; + + id = portConfigList->port(i).port_id().id(); + // FIXME: don't mix port id & index into mPorts[] + mPorts[id]->updatePortConfig(portConfigList->mutable_port(i)); + } + + //emit portListChanged(mPortGroupId); + + // FIXME: check if we need new signals since we are not changing the + // number of ports, just the port data + + if (numPorts() > 0) + getStreamIdList(); + +_error_exit: + delete controller; +} + +void PortGroup::when_configApply(int portIndex) +{ + OstProto::StreamIdList *streamIdList; + OstProto::StreamConfigList *streamConfigList; + OstProto::Ack *ack; + PbRpcController *controller; + + Q_ASSERT(portIndex < mPorts.size()); + + if (state() != QAbstractSocket::ConnectedState) + return; + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + mainWindow->setDisabled(true); + + qDebug("applying 'deleted streams' ..."); + streamIdList = new OstProto::StreamIdList; + ack = new OstProto::Ack; + controller = new PbRpcController(streamIdList, ack); + + streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getDeletedStreamsSinceLastSync(*streamIdList); + + serviceStub->deleteStream(controller, streamIdList, ack, + NewCallback(this, &PortGroup::processDeleteStreamAck, controller)); + + qDebug("applying 'new streams' ..."); + streamIdList = new OstProto::StreamIdList; + ack = new OstProto::Ack; + controller = new PbRpcController(streamIdList, ack); + + streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getNewStreamsSinceLastSync(*streamIdList); + + serviceStub->addStream(controller, streamIdList, ack, + NewCallback(this, &PortGroup::processAddStreamAck, controller)); + + qDebug("applying 'modified streams' ..."); + streamConfigList = new OstProto::StreamConfigList; + ack = new OstProto::Ack; + controller = new PbRpcController(streamConfigList, ack); + + streamConfigList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + mPorts[portIndex]->getModifiedStreamsSinceLastSync(*streamConfigList); + + serviceStub->modifyStream(controller, streamConfigList, ack, + NewCallback(this, &PortGroup::processModifyStreamAck, + portIndex, controller)); +} + +void PortGroup::processAddStreamAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + delete controller; +} + +void PortGroup::processDeleteStreamAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + delete controller; +} + +void PortGroup::processModifyStreamAck(int portIndex, + PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + qDebug("apply completed"); + mPorts[portIndex]->when_syncComplete(); + + mainWindow->setEnabled(true); + QApplication::restoreOverrideCursor(); + + delete controller; +} + +void PortGroup::modifyPort(int portIndex, OstProto::Port portConfig) +{ + OstProto::PortConfigList *portConfigList = new OstProto::PortConfigList; + OstProto::Ack *ack = new OstProto::Ack; + + qDebug("%s: portIndex = %d", __FUNCTION__, portIndex); + + Q_ASSERT(portIndex < mPorts.size()); + + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + mainWindow->setDisabled(true); + + OstProto::Port *port = portConfigList->add_port(); + port->CopyFrom(portConfig); + port->mutable_port_id()->set_id(mPorts[portIndex]->id()); + + PbRpcController *controller = new PbRpcController(portConfigList, ack); + serviceStub->modifyPort(controller, portConfigList, ack, + NewCallback(this, &PortGroup::processModifyPortAck, controller)); +} + +void PortGroup::processModifyPortAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _exit; + } + + { + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + OstProto::PortConfigList *portConfigList = new OstProto::PortConfigList; + PbRpcController *controller2 = new PbRpcController(portIdList, + portConfigList); + + OstProto::PortId *portId = portIdList->add_port_id(); + portId->CopyFrom(static_cast + (controller->request())->mutable_port(0)->port_id()); + + serviceStub->getPortConfig(controller, portIdList, portConfigList, + NewCallback(this, &PortGroup::processUpdatedPortConfig, + controller2)); + } +_exit: + delete controller; +} + +void PortGroup::processUpdatedPortConfig(PbRpcController *controller) +{ + OstProto::PortConfigList *portConfigList + = static_cast(controller->response()); + + qDebug("In %s", __FUNCTION__); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _exit; + } + + if (portConfigList->port_size() != 1) + qDebug("port size = %d (expected = 1)", portConfigList->port_size()); + + for(int i = 0; i < portConfigList->port_size(); i++) + { + uint id; + + id = portConfigList->port(i).port_id().id(); + // FIXME: don't mix port id & index into mPorts[] + mPorts[id]->updatePortConfig(portConfigList->mutable_port(i)); + + emit portGroupDataChanged(mPortGroupId, id); + } + + +_exit: + mainWindow->setEnabled(true); + QApplication::restoreOverrideCursor(); + delete controller; +} + +void PortGroup::getStreamIdList() +{ + for (int portIndex = 0; portIndex < numPorts(); portIndex++) + { + OstProto::PortId *portId = new OstProto::PortId; + OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; + PbRpcController *controller = new PbRpcController(portId, streamIdList); + + portId->set_id(mPorts[portIndex]->id()); + + serviceStub->getStreamIdList(controller, portId, streamIdList, + NewCallback(this, &PortGroup::processStreamIdList, + portIndex, controller)); + } +} + +void PortGroup::processStreamIdList(int portIndex, PbRpcController *controller) +{ + OstProto::StreamIdList *streamIdList + = static_cast(controller->response()); + + qDebug("In %s (portIndex = %d)", __FUNCTION__, portIndex); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _exit; + } + + Q_ASSERT(portIndex < numPorts()); + + if (streamIdList->port_id().id() != mPorts[portIndex]->id()) + { + qDebug("Invalid portId %d (expected %d) received for portIndex %d", + streamIdList->port_id().id(), mPorts[portIndex]->id(), portIndex); + goto _exit; + } + + for(int i = 0; i < streamIdList->stream_id_size(); i++) + { + uint streamId; + + streamId = streamIdList->stream_id(i).id(); + mPorts[portIndex]->insertStream(streamId); + } + + mPorts[portIndex]->when_syncComplete(); + + // Are we done for all ports? + if (numPorts() && portIndex >= (numPorts()-1)) + { + // FIXME(HI): some way to reset streammodel + getStreamConfigList(); + } + +_exit: + delete controller; +} + +void PortGroup::getStreamConfigList() +{ + qDebug("requesting stream config list ..."); + + for (int portIndex = 0; portIndex < numPorts(); portIndex++) + { + OstProto::StreamIdList *streamIdList = new OstProto::StreamIdList; + OstProto::StreamConfigList *streamConfigList + = new OstProto::StreamConfigList; + PbRpcController *controller = new PbRpcController( + streamIdList, streamConfigList); + + streamIdList->mutable_port_id()->set_id(mPorts[portIndex]->id()); + for (int j = 0; j < mPorts[portIndex]->numStreams(); j++) + { + OstProto::StreamId *s = streamIdList->add_stream_id(); + s->set_id(mPorts[portIndex]->streamByIndex(j)->id()); + } + + serviceStub->getStreamConfig(controller, streamIdList, streamConfigList, + NewCallback(this, &PortGroup::processStreamConfigList, + portIndex, controller)); + } +} + +void PortGroup::processStreamConfigList(int portIndex, + PbRpcController *controller) +{ + OstProto::StreamConfigList *streamConfigList + = static_cast(controller->response()); + + qDebug("In %s", __PRETTY_FUNCTION__); + + Q_ASSERT(portIndex < numPorts()); + + if (controller->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(controller->ErrorString())); + goto _exit; + } + + Q_ASSERT(portIndex < numPorts()); + + if (streamConfigList->port_id().id() != mPorts[portIndex]->id()) + { + qDebug("Invalid portId %d (expected %d) received for portIndex %d", + streamConfigList->port_id().id(), mPorts[portIndex]->id(), portIndex); + goto _exit; + } + + for(int i = 0; i < streamConfigList->stream_size(); i++) + { + uint streamId; + + streamId = streamConfigList->stream(i).stream_id().id(); + mPorts[portIndex]->updateStream(streamId, + streamConfigList->mutable_stream(i)); + } + + // Are we done for all ports? + if (portIndex >= numPorts()) + { + // FIXME(HI): some way to reset streammodel + } + +_exit: + delete controller; +} + +void PortGroup::startTx(QList *portList) +{ + qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + goto _exit; + + if (portList == NULL) + goto _exit; + + { + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + OstProto::Ack *ack = new OstProto::Ack; + PbRpcController *controller = new PbRpcController(portIdList, ack); + + for (int i = 0; i < portList->size(); i++) + { + OstProto::PortId *portId = portIdList->add_port_id(); + portId->set_id(portList->at(i)); + } + + serviceStub->startTransmit(controller, portIdList, ack, + NewCallback(this, &PortGroup::processStartTxAck, controller)); + } +_exit: + return; +} + +void PortGroup::processStartTxAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + delete controller; +} + +void PortGroup::stopTx(QList *portList) +{ + + qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + goto _exit; + + if ((portList == NULL) || (portList->size() == 0)) + goto _exit; + { + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + OstProto::Ack *ack = new OstProto::Ack; + PbRpcController *controller = new PbRpcController(portIdList, ack); + + for (int i = 0; i < portList->size(); i++) + { + OstProto::PortId *portId = portIdList->add_port_id(); + portId->set_id(portList->at(i)); + } + + serviceStub->stopTransmit(controller, portIdList, ack, + NewCallback(this, &PortGroup::processStopTxAck, controller)); + } +_exit: + return; +} + +void PortGroup::processStopTxAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + delete controller; +} + +void PortGroup::startCapture(QList *portList) +{ + qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + return; + + if ((portList == NULL) || (portList->size() == 0)) + goto _exit; + + { + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + OstProto::Ack *ack = new OstProto::Ack; + PbRpcController *controller = new PbRpcController(portIdList, ack); + + for (int i = 0; i < portList->size(); i++) + { + OstProto::PortId *portId = portIdList->add_port_id(); + portId->set_id(portList->at(i)); + } + + serviceStub->startCapture(controller, portIdList, ack, + NewCallback(this, &PortGroup::processStartCaptureAck, controller)); + } +_exit: + return; +} + +void PortGroup::processStartCaptureAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + delete controller; +} + +void PortGroup::stopCapture(QList *portList) +{ + qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + return; + + if ((portList == NULL) || (portList->size() == 0)) + goto _exit; + + { + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + OstProto::Ack *ack = new OstProto::Ack; + PbRpcController *controller = new PbRpcController(portIdList, ack); + + for (int i = 0; i < portList->size(); i++) + { + OstProto::PortId *portId = portIdList->add_port_id(); + portId->set_id(portList->at(i)); + } + + serviceStub->stopCapture(controller, portIdList, ack, + NewCallback(this, &PortGroup::processStopCaptureAck, controller)); + } +_exit: + return; +} + +void PortGroup::processStopCaptureAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + delete controller; +} + +void PortGroup::viewCapture(QList *portList) +{ + qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + goto _exit; + + if ((portList == NULL) || (portList->size() != 1)) + goto _exit; + + + for (int i = 0; i < portList->size(); i++) + { + OstProto::PortId *portId = new OstProto::PortId; + OstProto::CaptureBuffer *buf = new OstProto::CaptureBuffer; + PbRpcController *controller = new PbRpcController(portId, buf); + QFile *capFile = mPorts[portList->at(i)]->getCaptureFile(); + + portId->set_id(portList->at(i)); + + capFile->open(QIODevice::ReadWrite|QIODevice::Truncate); + qDebug("Temp CapFile = %s", capFile->fileName().toAscii().constData()); + controller->setBinaryBlob(capFile); + + serviceStub->getCaptureBuffer(controller, portId, buf, + NewCallback(this, &PortGroup::processViewCaptureAck, controller)); + } +_exit: + return; +} + +void PortGroup::processViewCaptureAck(PbRpcController *controller) +{ + QFile *capFile = static_cast(controller->binaryBlob()); + + QString viewer = appSettings->value(kWiresharkPathKey, + kWiresharkPathDefaultValue).toString(); + + qDebug("In %s", __FUNCTION__); + + capFile->flush(); + capFile->close(); + + if (!QFile::exists(viewer)) + { + QMessageBox::warning(NULL, "Can't find Wireshark", + viewer + QString(" does not exist!\n\nPlease correct the path" + " to Wireshark in the Preferences.")); + goto _exit; + } + + if (!QProcess::startDetached(viewer, QStringList() << capFile->fileName())) + qDebug("Failed starting Wireshark"); + +_exit: + delete controller; +} + +void PortGroup::getPortStats() +{ + //qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + goto _exit; + + if (numPorts() <= 0) + goto _exit; + + if (isGetStatsPending_) + goto _exit; + + statsController->Reset(); + isGetStatsPending_ = true; + serviceStub->getStats(statsController, + static_cast(statsController->request()), + static_cast(statsController->response()), + NewCallback(this, &PortGroup::processPortStatsList)); + +_exit: + return; +} + +void PortGroup::processPortStatsList() +{ + //qDebug("In %s", __FUNCTION__); + + if (statsController->Failed()) + { + qDebug("%s: rpc failed(%s)", __FUNCTION__, + qPrintable(statsController->ErrorString())); + goto _error_exit; + } + + for(int i = 0; i < portStatsList_->port_stats_size(); i++) + { + uint id = portStatsList_->port_stats(i).port_id().id(); + // FIXME: don't mix port id & index into mPorts[] + mPorts[id]->updateStats(portStatsList_->mutable_port_stats(i)); + } + + emit statsChanged(mPortGroupId); + +_error_exit: + isGetStatsPending_ = false; +} + +void PortGroup::clearPortStats(QList *portList) +{ + qDebug("In %s", __FUNCTION__); + + if (state() != QAbstractSocket::ConnectedState) + goto _exit; + + { + OstProto::PortIdList *portIdList = new OstProto::PortIdList; + OstProto::Ack *ack = new OstProto::Ack; + PbRpcController *controller = new PbRpcController(portIdList, ack); + + if (portList == NULL) + portIdList->CopyFrom(*portIdList_); + else + { + for (int i = 0; i < portList->size(); i++) + { + OstProto::PortId *portId = portIdList->add_port_id(); + portId->set_id(portList->at(i)); + } + } + + serviceStub->clearStats(controller, portIdList, ack, + NewCallback(this, &PortGroup::processClearStatsAck, controller)); + } +_exit: + return; +} + +void PortGroup::processClearStatsAck(PbRpcController *controller) +{ + qDebug("In %s", __FUNCTION__); + + // Refresh stats immediately after a stats clear/reset + getPortStats(); + + delete controller; +} + diff --git a/client/portgroup.h b/client/portgroup.h new file mode 100644 index 0000000..ccb8db8 --- /dev/null +++ b/client/portgroup.h @@ -0,0 +1,157 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORT_GROUP_H +#define _PORT_GROUP_H + +#include "port.h" +#include +#include + +#include "../common/protocol.pb.h" +#include "pbrpcchannel.h" + +/* TODO +HIGH +MED +LOW +- Allow hostnames in addition to IP Address as "server address" +*/ + +#define DEFAULT_SERVER_PORT 7878 + +class QFile; +class QTimer; + +class PortGroup : public QObject { + Q_OBJECT + +private: + enum { kIncompatible, kCompatible, kUnknown } compat; + static quint32 mPortGroupAllocId; + quint32 mPortGroupId; + QString mUserAlias; // user defined + + bool reconnect; + int reconnectAfter; // time in milliseconds + static const int kMinReconnectWaitTime = 2000; // ms + static const int kMaxReconnectWaitTime = 60000; // ms + QTimer *reconnectTimer; + PbRpcChannel *rpcChannel; + PbRpcController *statsController; + bool isGetStatsPending_; + + OstProto::OstService::Stub *serviceStub; + + OstProto::PortIdList *portIdList_; + OstProto::PortStatsList *portStatsList_; + +public: // FIXME(HIGH): member access + QList mPorts; + +public: + PortGroup(QHostAddress ip = QHostAddress::LocalHost, + quint16 port = DEFAULT_SERVER_PORT); + ~PortGroup(); + + void connectToHost() { + reconnect = true; + compat = kUnknown; + rpcChannel->establish(); + } + void connectToHost(QHostAddress ip, quint16 port) { + reconnect = true; + compat = kUnknown; + rpcChannel->establish(ip, port); + } + void disconnectFromHost() { reconnect = false; rpcChannel->tearDown(); } + + int numPorts() const { return mPorts.size(); } + quint32 id() const { return mPortGroupId; } + + const QString& userAlias() const { return mUserAlias; } + void setUserAlias(QString alias) { mUserAlias = alias; }; + + const QHostAddress& serverAddress() const + { return rpcChannel->serverAddress(); } + quint16 serverPort() const + { return rpcChannel->serverPort(); } + QAbstractSocket::SocketState state() const { + if (compat == kIncompatible) + return QAbstractSocket::SocketState(-1); + return rpcChannel->state(); + } + + void processVersionCompatibility(PbRpcController *controller); + void processPortIdList(PbRpcController *controller); + void processPortConfigList(PbRpcController *controller); + + void processAddStreamAck(PbRpcController *controller); + void processDeleteStreamAck(PbRpcController *controller); + void processModifyStreamAck(int portIndex, PbRpcController *controller); + + void modifyPort(int portId, OstProto::Port portConfig); + void processModifyPortAck(PbRpcController *controller); + void processUpdatedPortConfig(PbRpcController *controller); + + void getStreamIdList(); + void processStreamIdList(int portIndex, PbRpcController *controller); + void getStreamConfigList(); + void processStreamConfigList(int portIndex, PbRpcController *controller); + + void processModifyStreamAck(OstProto::Ack *ack); + + void startTx(QList *portList = NULL); + void processStartTxAck(PbRpcController *controller); + void stopTx(QList *portList = NULL); + void processStopTxAck(PbRpcController *controller); + + void startCapture(QList *portList = NULL); + void processStartCaptureAck(PbRpcController *controller); + void stopCapture(QList *portList = NULL); + void processStopCaptureAck(PbRpcController *controller); + void viewCapture(QList *portList = NULL); + void processViewCaptureAck(PbRpcController *controller); + + void getPortStats(); + void processPortStatsList(); + void clearPortStats(QList *portList = NULL); + void processClearStatsAck(PbRpcController *controller); + +signals: + void portGroupDataChanged(int portGroupId, int portId = 0xFFFF); + void portListAboutToBeChanged(quint32 portGroupId); + void portListChanged(quint32 portGroupId); + void statsChanged(quint32 portGroupId); + +private slots: + void on_reconnectTimer_timeout(); + void on_rpcChannel_stateChanged(QAbstractSocket::SocketState state); + void on_rpcChannel_connected(); + void on_rpcChannel_disconnected(); + void on_rpcChannel_error(QAbstractSocket::SocketError socketError); + + void when_portListChanged(quint32 portGroupId); + +public slots: + void when_configApply(int portIndex); + +}; + +#endif diff --git a/client/portgrouplist.cpp b/client/portgrouplist.cpp new file mode 100644 index 0000000..cfdc74b --- /dev/null +++ b/client/portgrouplist.cpp @@ -0,0 +1,133 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "portgrouplist.h" + +// TODO(LOW): Remove +#include + +PortGroupList::PortGroupList() + : mPortGroupListModel(this), + mStreamListModel(this), + mPortStatsModel(this, this) +{ + PortGroup *pg; + +#ifdef QT_NO_DEBUG + streamModelTester_ = NULL; + portModelTester_ = NULL; + portStatsModelTester_ = NULL; +#else + streamModelTester_ = new ModelTest(getStreamModel()); + portModelTester_ = new ModelTest(getPortModel()); + portStatsModelTester_ = new ModelTest(getPortStatsModel()); +#endif + + // Add the "Local" Port Group + pg = new PortGroup; + addPortGroup(*pg); +} + +PortGroupList::~PortGroupList() +{ + delete portStatsModelTester_; + delete portModelTester_; + delete streamModelTester_; + + while (!mPortGroups.isEmpty()) + delete mPortGroups.takeFirst(); + +} + +bool PortGroupList::isPortGroup(const QModelIndex& index) +{ + return mPortGroupListModel.isPortGroup(index); +} + +bool PortGroupList::isPort(const QModelIndex& index) +{ + return mPortGroupListModel.isPort(index); +} + +PortGroup& PortGroupList::portGroup(const QModelIndex& index) +{ + Q_ASSERT(mPortGroupListModel.isPortGroup(index)); + + return *(mPortGroups[index.row()]); +} + +Port& PortGroupList::port(const QModelIndex& index) +{ + Q_ASSERT(mPortGroupListModel.isPort(index)); + return (*mPortGroups.at(index.parent().row())->mPorts[index.row()]); +} + +void PortGroupList::addPortGroup(PortGroup &portGroup) +{ + mPortGroupListModel.portGroupAboutToBeAppended(); + + connect(&portGroup, SIGNAL(portGroupDataChanged(int, int)), + &mPortGroupListModel, SLOT(when_portGroupDataChanged(int, int))); +#if 0 + connect(&portGroup, SIGNAL(portListAboutToBeChanged(quint32)), + &mPortGroupListModel, SLOT(triggerLayoutAboutToBeChanged())); + connect(&portGroup, SIGNAL(portListChanged(quint32)), + &mPortGroupListModel, SLOT(triggerLayoutChanged())); +#endif + connect(&portGroup, SIGNAL(portListChanged(quint32)), + &mPortGroupListModel, SLOT(when_portListChanged())); + + connect(&portGroup, SIGNAL(portListChanged(quint32)), + &mPortStatsModel, SLOT(when_portListChanged())); + + connect(&portGroup, SIGNAL(statsChanged(quint32)), + &mPortStatsModel, SLOT(when_portGroup_stats_update(quint32))); + + mPortGroups.append(&portGroup); + portGroup.connectToHost(); + + mPortGroupListModel.portGroupAppended(); + + mPortStatsModel.when_portListChanged(); +} + +void PortGroupList::removePortGroup(PortGroup &portGroup) +{ + mPortGroupListModel.portGroupAboutToBeRemoved(&portGroup); + + PortGroup* pg = mPortGroups.takeAt(mPortGroups.indexOf(&portGroup)); + qDebug("after takeAt()"); + mPortGroupListModel.portGroupRemoved(); + + delete pg; + + mPortStatsModel.when_portListChanged(); +} + +//.................... +// Private Methods +//.................... +int PortGroupList::indexOfPortGroup(quint32 portGroupId) +{ + for (int i = 0; i < mPortGroups.size(); i++) { + if (mPortGroups.value(i)->id() == portGroupId) + return i; + } + return -1; +} diff --git a/client/portgrouplist.h b/client/portgrouplist.h new file mode 100644 index 0000000..3083c26 --- /dev/null +++ b/client/portgrouplist.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORT_GROUP_LIST_H +#define _PORT_GROUP_LIST_H + +#include "portgroup.h" +#include +#include +#include "portmodel.h" +#include "streammodel.h" +#include "portstatsmodel.h" + +class PortModel; +class StreamModel; + +class PortGroupList : public QObject { + + Q_OBJECT + + friend class PortModel; + friend class StreamModel; + friend class PortStatsModel; + + QList mPortGroups; + PortModel mPortGroupListModel; + StreamModel mStreamListModel; + PortStatsModel mPortStatsModel; + + QObject *streamModelTester_; + QObject *portModelTester_; + QObject *portStatsModelTester_; + +// Methods +public: + PortGroupList(); + ~PortGroupList(); + + PortModel* getPortModel() { return &mPortGroupListModel; } + PortStatsModel* getPortStatsModel() { return &mPortStatsModel; } + StreamModel* getStreamModel() { return &mStreamListModel; } + + bool isPortGroup(const QModelIndex& index); + bool isPort(const QModelIndex& index); + PortGroup& portGroup(const QModelIndex& index); + Port& port(const QModelIndex& index); + + int numPortGroups() { return mPortGroups.size(); } + PortGroup& portGroupByIndex(int index) { return *(mPortGroups[index]); } + + void addPortGroup(PortGroup &portGroup); + void removePortGroup(PortGroup &portGroup); + +private: + int indexOfPortGroup(quint32 portGroupId); + +}; + +#endif diff --git a/client/portmodel.cpp b/client/portmodel.cpp new file mode 100644 index 0000000..01da997 --- /dev/null +++ b/client/portmodel.cpp @@ -0,0 +1,343 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "portmodel.h" +#include "portgrouplist.h" + +#include +#include + +#if 0 +#define DBG0(x) qDebug(x) +#define DBG1(x, p1) qDebug(x, (p1)) +#else +#define DBG0(x) {} +#define DBG1(x, p1) {} +#endif + +PortModel::PortModel(PortGroupList *p, QObject *parent) + : QAbstractItemModel(parent) +{ + pgl = p; + + portIconFactory[OstProto::LinkStateUnknown][false] = + QIcon(":/icons/bullet_white.png"); + portIconFactory[OstProto::LinkStateDown][false] = + QIcon(":/icons/bullet_red.png"); + portIconFactory[OstProto::LinkStateUp][false] = + QIcon(":/icons/bullet_green.png"); + + for (int linkState = 0; linkState < kLinkStatesCount; linkState++) + { + QPixmap pixmap(":/icons/deco_exclusive.png"); + QPainter painter(&pixmap); + QIcon icon = portIconFactory[linkState][false]; + + painter.drawPixmap(0, 0, icon.pixmap(QSize(32,32))); + portIconFactory[linkState][true] = QIcon(pixmap); + } +} + +int PortModel::rowCount(const QModelIndex &parent) const +{ + // qDebug("RowCount Enter\n"); + if (!parent.isValid()) + { + // Top Level Item + //qDebug("RowCount (Top) Exit: %d\n", pgl->mPortGroups.size()); + return pgl->mPortGroups.size(); + } + // qDebug("RowCount non top %d, %d, %llx\n", + // parent.row(), parent.column(), parent.internalId()); + + quint16 pg = (parent.internalId() >> 16) & 0xFFFF; + quint16 p = parent.internalId() & 0xFFFF; + if (p == 0xFFFF) + { +#if 0 // wrong code? + int count = 0; + foreach(PortGroup *pg, pgl->mPortGroups) + { + count += pg->numPorts(); + } + //qDebug("RowCount (Mid) Exit: %d\n", count); + return count; +#endif + if (parent.column() == 0) + return pgl->mPortGroups.value(pgl->indexOfPortGroup(pg))->numPorts(); + else + return 0; + } + else + { + // Leaf Item + return 0; + } +} + +int PortModel::columnCount(const QModelIndex &/*parent*/) const +{ + return 1; // FIXME: hardcoding +} + +Qt::ItemFlags PortModel::flags(const QModelIndex &index) const +{ + return QAbstractItemModel::flags(index); // FIXME: no need for this func +} +QVariant PortModel::data(const QModelIndex &index, int role) const +{ + + DBG0("Enter PortModel data\n"); + + // Check for a valid index + if (!index.isValid()) + return QVariant(); + + DBG1("PortModel::data(index).row = %d", index.row()); + DBG1("PortModel::data(index).column = %0d", index.column()); + DBG1("PortModel::data(index).internalId = %08llx", index.internalId()); + + QModelIndex parent = index.parent(); + + if (!parent.isValid()) + { + // Top Level Item - PortGroup + if ((role == Qt::DisplayRole)) + { + DBG0("Exit PortModel data 1\n"); + return QString("Port Group %1: %2 [%3:%4] (%5)"). + arg(pgl->mPortGroups.at(index.row())->id()). + arg(pgl->mPortGroups.at(index.row())->userAlias()). + arg(pgl->mPortGroups.at(index.row())->serverAddress().toString()). + arg(pgl->mPortGroups.at(index.row())->serverPort()). + arg(pgl->mPortGroups.value(index.row())->numPorts()); + } + else if ((role == Qt::DecorationRole)) + { + DBG0("Exit PortModel data 2\n"); + switch(pgl->mPortGroups.at(index.row())->state()) + { + case QAbstractSocket::UnconnectedState: + return QIcon(":/icons/bullet_red.png"); + + case QAbstractSocket::HostLookupState: + return QIcon(":/icons/bullet_yellow.png"); + + case QAbstractSocket::ConnectingState: + case QAbstractSocket::ClosingState: + return QIcon(":/icons/bullet_orange.png"); + + case QAbstractSocket::ConnectedState: + return QIcon(":/icons/bullet_green.png"); + + + case QAbstractSocket::BoundState: + case QAbstractSocket::ListeningState: + default: + return QIcon(":/icons/bullet_error.png"); + } + } + else + { + DBG0("Exit PortModel data 3\n"); + return QVariant(); + } + } + else + { + if (pgl->mPortGroups.at(parent.row())->numPorts() == 0) + { + DBG0("Exit PortModel data 4\n"); + return QVariant(); + } + + Port *port = pgl->mPortGroups.at(parent.row())->mPorts[index.row()]; + + // Non Top Level - Port + if ((role == Qt::DisplayRole)) + { + QString rsvdBy; + + if (!port->userName().isEmpty()) + rsvdBy = "["+port->userName()+"] "; + + return QString("Port %1: %2 %3(%4)") + .arg(port->id()) + .arg(port->name()) + .arg(rsvdBy) + .arg(port->description()); + } + else if ((role == Qt::DecorationRole)) + { + return portIconFactory[port->linkState()][port->hasExclusiveControl()]; + } + else + { + DBG0("Exit PortModel data 6\n"); + return QVariant(); + } + } + + return QVariant(); +} + +QVariant PortModel::headerData(int /*section*/, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + return QVariant(); + else + return QString("Name"); +} + +QModelIndex PortModel::index (int row, int col, + const QModelIndex & parent) const +{ + if (!hasIndex(row, col, parent)) + return QModelIndex(); + + //qDebug("index: R=%d, C=%d, PR=%d, PC=%d, PID=%llx\n", + // row, col, parent.row(), parent.column(), parent.internalId()); + + if (!parent.isValid()) + { + // Top Level Item + quint16 pg = pgl->mPortGroups.value(row)->id(), p = 0xFFFF; + quint32 id = (pg << 16) | p; + //qDebug("index (top) dbg: PG=%d, P=%d, ID=%x\n", pg, p, id); + + return createIndex(row, col, id); + } + else + { + quint16 pg = parent.internalId() >> 16; + quint16 p = pgl->mPortGroups.value(parent.row())->mPorts.value(row)->id(); + quint32 id = (pg << 16) | p; + //qDebug("index (nontop) dbg: PG=%d, P=%d, ID=%x\n", pg, p, id); + + return createIndex(row, col, id); + } +} + +QModelIndex PortModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + //qDebug("parent: R=%d, C=%d ID=%llx\n", + // index.row(), index.column(), index.internalId()); + + quint16 pg = index.internalId() >> 16; + quint16 p = index.internalId() & 0x0000FFFF; + + //qDebug("parent dbg: PG=%d, P=%d\n", pg, p); + + if (p == 0xFFFF) + { + //qDebug("parent ret: NULL\n"); + // Top Level Item - PG + return QModelIndex(); + } + + quint32 id = (pg << 16) | 0xFFFF; + //qDebug("parent ret: R=%d, C=%d, ID=%x\n", pg, 0, id); + + return createIndex(pgl->indexOfPortGroup(pg), 0, id); + +} + +bool PortModel::isPortGroup(const QModelIndex& index) +{ + if (index.isValid() && ((index.internalId() & 0xFFFF) == 0xFFFF)) + return true; + else + return false; +} + +bool PortModel::isPort(const QModelIndex& index) +{ + if (index.isValid() && ((index.internalId() & 0xFFFF) != 0xFFFF)) + return true; + else + return false; +} + +quint32 PortModel::portGroupId(const QModelIndex& index) +{ + return (index.internalId()) >> 16 & 0xFFFF; +} + +quint32 PortModel::portId(const QModelIndex& index) +{ + return (index.internalId()) & 0xFFFF; +} + + + +// ---------------------------------------------- +// Slots +// ---------------------------------------------- +void PortModel::when_portGroupDataChanged(int portGroupId, int portId) +{ + QModelIndex index; + int row; + + qDebug("portGroupId = %d, portId = %d", portGroupId, portId); + if (portId == 0xFFFF) + row = pgl->indexOfPortGroup(portGroupId); + else + row = portId; + + index = createIndex(row, 0, (portGroupId << 16) | portId); + + emit dataChanged(index, index); +} + +void PortModel::portGroupAboutToBeAppended() +{ + int row; + + row = pgl->mPortGroups.size(); + beginInsertRows(QModelIndex(), row, row); +} + +void PortModel::portGroupAppended() +{ + endInsertRows(); +} + +void PortModel::portGroupAboutToBeRemoved(PortGroup *portGroup) +{ + int row; + + row = pgl->mPortGroups.indexOf(portGroup); + beginRemoveRows(QModelIndex(), row, row); +} + +void PortModel::portGroupRemoved() +{ + endRemoveRows(); +} + +void PortModel::when_portListChanged() +{ + reset(); +} diff --git a/client/portmodel.h b/client/portmodel.h new file mode 100644 index 0000000..2027f0b --- /dev/null +++ b/client/portmodel.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORT_MODEL_H +#define _PORT_MODEL_H + +#include +#include + +class PortGroupList; +class PortGroup; + +class PortModel : public QAbstractItemModel +{ + Q_OBJECT + + friend class PortGroupList; + +public: + PortModel(PortGroupList *p, QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + QModelIndex index (int row, int col, + const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &index) const; + + bool isPortGroup(const QModelIndex& index); + bool isPort(const QModelIndex& index); + quint32 portGroupId(const QModelIndex& index); + quint32 portId(const QModelIndex& index); + +private: + PortGroupList *pgl; + static const int kLinkStatesCount = 3; + static const int kExclusiveStatesCount = 2; + QIcon portIconFactory[kLinkStatesCount][kExclusiveStatesCount]; + +private slots: + void when_portGroupDataChanged(int portGroupId, int portId); + + void portGroupAboutToBeAppended(); + void portGroupAppended(); + void portGroupAboutToBeRemoved(PortGroup *portGroup); + void portGroupRemoved(); + + void when_portListChanged(); + +#if 0 + void triggerLayoutAboutToBeChanged(); + void triggerLayoutChanged(); +#endif +}; + +#endif diff --git a/client/portstatsfilter.ui b/client/portstatsfilter.ui new file mode 100644 index 0000000..61aa7a7 --- /dev/null +++ b/client/portstatsfilter.ui @@ -0,0 +1,170 @@ + + PortStatsFilterDialog + + + + 0 + 0 + 319 + 193 + + + + Select Ports + + + :/icons/portstats_filter.png + + + + + + + + false + + + false + + + QAbstractItemView::NoDragDrop + + + QAbstractItemView::ExtendedSelection + + + QListView::Static + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + > + + + :/icons/arrow_right.png + + + + + + + < + + + :/icons/arrow_left.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + true + + + true + + + false + + + QAbstractItemView::InternalMove + + + QAbstractItemView::ExtendedSelection + + + QListView::Free + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + lvUnselected + tbSelectIn + tbSelectOut + lvSelected + buttonBox + + + + + + + buttonBox + accepted() + PortStatsFilterDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + PortStatsFilterDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/client/portstatsfilterdialog.cpp b/client/portstatsfilterdialog.cpp new file mode 100644 index 0000000..c2aec10 --- /dev/null +++ b/client/portstatsfilterdialog.cpp @@ -0,0 +1,134 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "portstatsfilterdialog.h" + +PortStatsFilterDialog::PortStatsFilterDialog(QWidget *parent) + : QDialog(parent) +{ + setupUi(this); + + mUnselected.setSortRole(kLogicalIndex); + mSelected.setSortRole(kVisualIndex); + + lvUnselected->setModel(&mUnselected); + lvSelected->setModel(&mSelected); +} + +QList PortStatsFilterDialog::getItemList(bool* ok, + QAbstractItemModel *model, Qt::Orientation orientation, + QList initial) +{ + QList ret; + + uint count = (orientation == Qt::Vertical) ? + model->rowCount() : model->columnCount(); + + *ok = false; + + mUnselected.clear(); + mSelected.clear(); + + for (uint i = 0; i < count; i++) + { + QStandardItem *item; + + item = new QStandardItem(model->headerData(i, orientation).toString()); + item->setData(i, kLogicalIndex); + item->setData(initial.indexOf(i), kVisualIndex); + item->setFlags(Qt::ItemIsSelectable + | Qt::ItemIsDragEnabled + //| Qt::ItemIsDropEnabled + | Qt::ItemIsEnabled); + + if (initial.contains(i)) + mSelected.appendRow(item); + else + mUnselected.appendRow(item); + } + mSelected.sort(0); + + // No need to sort right now 'coz we have inserted items in order + + if (exec() == QDialog::Accepted) + { + uint count = mSelected.rowCount(); + for (uint i = 0; i < count; i++) + { + QModelIndex index = mSelected.index(i, 0, QModelIndex()); + QStandardItem *item = mSelected.itemFromIndex(index); + ret.append(item->data(kLogicalIndex).toInt()); + } + *ok = true; + } + + return ret; +} + +void PortStatsFilterDialog::on_tbSelectIn_clicked() +{ + QList rows; + + foreach(QModelIndex idx, lvUnselected->selectionModel()->selectedIndexes()) + rows.append(idx.row()); + qSort(rows.begin(), rows.end(), qGreater()); + + QModelIndex idx = lvSelected->selectionModel()->currentIndex(); + int insertAt = idx.isValid() ? idx.row() : mSelected.rowCount(); + + foreach(int row, rows) + { + QList items = mUnselected.takeRow(row); + mSelected.insertRow(insertAt, items); + } +} + +void PortStatsFilterDialog::on_tbSelectOut_clicked() +{ + QList rows; + + foreach(QModelIndex idx, lvSelected->selectionModel()->selectedIndexes()) + rows.append(idx.row()); + qSort(rows.begin(), rows.end(), qGreater()); + + foreach(int row, rows) + { + QList items = mSelected.takeRow(row); + mUnselected.appendRow(items); + } + + mUnselected.sort(0); +} + +void PortStatsFilterDialog::on_lvUnselected_doubleClicked(const QModelIndex &index) +{ + QList items = mUnselected.takeRow(index.row()); + QModelIndex idx = lvSelected->selectionModel()->currentIndex(); + int insertAt = idx.isValid() ? idx.row() : mSelected.rowCount(); + + mSelected.insertRow(insertAt, items); +} + +void PortStatsFilterDialog::on_lvSelected_doubleClicked(const QModelIndex &index) +{ + QList items = mSelected.takeRow(index.row()); + mUnselected.appendRow(items); + mUnselected.sort(0); +} + diff --git a/client/portstatsfilterdialog.h b/client/portstatsfilterdialog.h new file mode 100644 index 0000000..97a0d37 --- /dev/null +++ b/client/portstatsfilterdialog.h @@ -0,0 +1,55 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORT_STATS_FILTER_DIALOG_H +#define _PORT_STATS_FILTER_DIALOG_H + +#include +#include +#include +#include "ui_portstatsfilter.h" +#include "portgrouplist.h" + +class PortStatsFilterDialog : public QDialog, public Ui::PortStatsFilterDialog +{ + Q_OBJECT + +public: + PortStatsFilterDialog(QWidget *parent = 0); + QList getItemList(bool* ok, QAbstractItemModel *model, + Qt::Orientation orientation = Qt::Vertical, + QList initial = QList()); + +private: + enum ItemRole { + kLogicalIndex = Qt::UserRole + 1, + kVisualIndex + }; + QStandardItemModel mUnselected; + QStandardItemModel mSelected; + +private slots: + void on_tbSelectIn_clicked(); + void on_tbSelectOut_clicked(); + void on_lvUnselected_doubleClicked(const QModelIndex &index); + void on_lvSelected_doubleClicked(const QModelIndex &index); +}; + +#endif + diff --git a/client/portstatsmodel.cpp b/client/portstatsmodel.cpp new file mode 100644 index 0000000..1658980 --- /dev/null +++ b/client/portstatsmodel.cpp @@ -0,0 +1,326 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "portstatsmodel.h" +#include "portgrouplist.h" + +#include + +PortStatsModel::PortStatsModel(PortGroupList *p, QObject *parent) + : QAbstractTableModel(parent) +{ + pgl = p; + + timer = new QTimer(); + connect(timer, SIGNAL(timeout()), this, SLOT(updateStats())); + timer->start(1000); +} + +PortStatsModel::~PortStatsModel() +{ + timer->stop(); + delete timer; +} + +int PortStatsModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + if (numPorts.isEmpty()) + return 0; + + if (numPorts.last() == 0) + return 0; + + return (int) e_STAT_MAX; +} + +int PortStatsModel::columnCount(const QModelIndex &parent ) const +{ + if (parent.isValid()) + return 0; + else + if (numPorts.isEmpty()) + return 0; + else + return numPorts.last(); +} + +void PortStatsModel::getDomainIndexes(const QModelIndex &index, + uint &portGroupIdx, uint &portIdx) const +{ + int portNum; + + // TODO(LOW): Optimize using binary search: see qLowerBound() + portNum = index.column() + 1; + for (portGroupIdx = 0; portGroupIdx < (uint) numPorts.size(); portGroupIdx++) + if (portNum <= numPorts.at(portGroupIdx)) + break; + + if (portGroupIdx) + { + if (numPorts.at(portGroupIdx -1)) + portIdx = (portNum - 1) - numPorts.at(portGroupIdx - 1); + else + portIdx = portNum - 1; + } + else + portIdx = portNum - 1; + + //qDebug("PSM: %d - %d, %d", index.column(), portGroupIdx, portIdx); +} + +QVariant PortStatsModel::data(const QModelIndex &index, int role) const +{ + uint pgidx, pidx; + int row; + + // Check for a valid index + if (!index.isValid()) + return QVariant(); + + // Check for row/column limits + row = index.row(); + if (row >= e_STAT_MAX) + return QVariant(); + + if (numPorts.isEmpty()) + return QVariant(); + + if (index.column() >= (numPorts.last())) + return QVariant(); + + getDomainIndexes(index, pgidx, pidx); + + // Check role + if (role == Qt::DisplayRole) + { + OstProto::PortStats stats; + + stats = pgl->mPortGroups.at(pgidx)->mPorts[pidx]->getStats(); + + switch(row) + { + // States + case e_LINK_STATE: + return LinkStateName.at(stats.state().link_state()); + + case e_TRANSMIT_STATE: + return BoolStateName.at(stats.state().is_transmit_on()); + + case e_CAPTURE_STATE: + return BoolStateName.at(stats.state().is_capture_on()); + + // Statistics + case e_STAT_FRAMES_RCVD: + return quint64(stats.rx_pkts()); + + case e_STAT_FRAMES_SENT: + return quint64(stats.tx_pkts()); + + case e_STAT_FRAME_SEND_RATE: + return quint64(stats.tx_pps()); + + case e_STAT_FRAME_RECV_RATE: + return quint64(stats.rx_pps()); + + case e_STAT_BYTES_RCVD: + return quint64(stats.rx_bytes()); + + case e_STAT_BYTES_SENT: + return quint64(stats.tx_bytes()); + + case e_STAT_BYTE_SEND_RATE: + return quint64(stats.tx_bps()); + + case e_STAT_BYTE_RECV_RATE: + return quint64(stats.rx_bps()); + +#if 0 + case e_STAT_FRAMES_RCVD_NIC: + return stats.rx_pkts_nic(); + + case e_STAT_FRAMES_SENT_NIC: + return stats.tx_pkts_nic(); + + case e_STAT_BYTES_RCVD_NIC: + return stats.rx_bytes_nic(); + + case e_STAT_BYTES_SENT_NIC: + return stats.tx_bytes_nic(); +#endif + case e_STAT_RX_DROPS : return quint64(stats.rx_drops()); + case e_STAT_RX_ERRORS: return quint64(stats.rx_errors()); + case e_STAT_RX_FIFO_ERRORS: return quint64(stats.rx_fifo_errors()); + case e_STAT_RX_FRAME_ERRORS: return quint64(stats.rx_frame_errors()); + + default: + qWarning("%s: Unhandled stats id %d\n", __FUNCTION__, + index.row()); + return 0; + } + } + else if (role == Qt::TextAlignmentRole) + { + if (row >= e_STATE_START && row <= e_STATE_END) + return Qt::AlignHCenter; + else if (row >= e_STATISTICS_START && row <= e_STATISTICS_END) + return Qt::AlignRight; + else + return QVariant(); + } + else + return QVariant(); + +} + +QVariant PortStatsModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role == Qt::ToolTipRole) + { + if (orientation == Qt::Horizontal) + { + QString notes; + uint portGroupIdx, portIdx; + + if (numPorts.isEmpty() || section >= numPorts.last()) + return QVariant(); + getDomainIndexes(index(0, section), portGroupIdx, portIdx); + notes = pgl->mPortGroups.at(portGroupIdx)->mPorts[portIdx]->notes(); + if (!notes.isEmpty()) + return notes; + else + return QVariant(); + } + else + return QVariant(); + } + + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + { + uint portGroupIdx, portIdx; + QString portName; + + if (numPorts.isEmpty() || section >= numPorts.last()) + return QVariant(); + getDomainIndexes(index(0, section), portGroupIdx, portIdx); + portName = QString("Port %1-%2") + .arg(pgl->mPortGroups.at(portGroupIdx)->id()) + .arg(pgl->mPortGroups.at(portGroupIdx)->mPorts.at(portIdx)->id()); + if (portGroupIdx < (uint) pgl->mPortGroups.size() + && portIdx < (uint) pgl->mPortGroups.at(portGroupIdx)->mPorts.size()) + { + if (!pgl->mPortGroups.at(portGroupIdx)->mPorts[portIdx]->notes() + .isEmpty()) + portName += " *"; + } + return portName; + } + else + return PortStatName.at(section); +} + +void PortStatsModel::portListFromIndex(QModelIndexList indices, + QList &portList) +{ + int i, j; + QModelIndexList selectedCols(indices); + + portList.clear(); + + //selectedCols = indices.selectedColumns(); + for (i = 0; i < selectedCols.size(); i++) + { + uint portGroupIdx, portIdx; + + getDomainIndexes(selectedCols.at(i), portGroupIdx, portIdx); + for (j = 0; j < portList.size(); j++) + { + if (portList[j].portGroupId == portGroupIdx) + break; + } + + if (j >= portList.size()) + { + // PortGroup Not found + PortGroupAndPortList p; + + p.portGroupId = portGroupIdx; + p.portList.append(portIdx); + + portList.append(p); + } + else + { + // PortGroup found + + portList[j].portList.append(portIdx); + } + } +} + +// +// Slots +// +void PortStatsModel::when_portListChanged() +{ + int i, count = 0; + + // recalc numPorts + while (numPorts.size()) + numPorts.removeFirst(); + + for (i = 0; i < pgl->mPortGroups.size(); i++) + { + count += pgl->mPortGroups.at(i)->numPorts(); + numPorts.append(count); + } + + reset(); +} + +void PortStatsModel::on_portStatsUpdate(int port, void* /*stats*/) +{ + QModelIndex topLeft = index(port, 0, QModelIndex()); + QModelIndex bottomRight = index(port, e_STAT_MAX, QModelIndex()); + + emit dataChanged(topLeft, bottomRight); +} + +void PortStatsModel::updateStats() +{ + // Request each portgroup to fetch updated stats - the port group + // raises a signal once updated stats are available + for (int i = 0; i < pgl->mPortGroups.size(); i++) + pgl->mPortGroups[i]->getPortStats(); +} + +void PortStatsModel::when_portGroup_stats_update(quint32 /*portGroupId*/) +{ + // FIXME(MED): update only the changed ports, not all + + QModelIndex topLeft = index(0, 0, QModelIndex()); + QModelIndex bottomRight = index(rowCount(), columnCount(), QModelIndex()); + + emit dataChanged(topLeft, bottomRight); +} diff --git a/client/portstatsmodel.h b/client/portstatsmodel.h new file mode 100644 index 0000000..a0d6868 --- /dev/null +++ b/client/portstatsmodel.h @@ -0,0 +1,151 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORT_STATS_MODEL_H +#define _PORT_STATS_MODEL_H + +#include +#include + +class QTimer; + +typedef enum { + // State + e_STATE_START = 0, + + e_LINK_STATE = e_STATE_START, + e_TRANSMIT_STATE, + e_CAPTURE_STATE, + + e_STATE_END = e_CAPTURE_STATE, + + // Statistics + e_STATISTICS_START, + + e_STAT_FRAMES_RCVD = e_STATISTICS_START, + e_STAT_FRAMES_SENT, + e_STAT_FRAME_SEND_RATE, + e_STAT_FRAME_RECV_RATE, + e_STAT_BYTES_RCVD, + e_STAT_BYTES_SENT, + e_STAT_BYTE_SEND_RATE, + e_STAT_BYTE_RECV_RATE, +#if 0 + e_STAT_FRAMES_RCVD_NIC, + e_STAT_FRAMES_SENT_NIC, + e_STAT_BYTES_RCVD_NIC, + e_STAT_BYTES_SENT_NIC, +#endif + + // Rx Errors + e_STAT_RX_DROPS, + e_STAT_RX_ERRORS, + e_STAT_RX_FIFO_ERRORS, + e_STAT_RX_FRAME_ERRORS, + + e_STATISTICS_END = e_STAT_RX_FRAME_ERRORS, + + e_STAT_MAX +} PortStat; + +static QStringList PortStatName = (QStringList() + << "Link State" + << "Transmit State" + << "Capture State" + + << "Frames Received" + << "Frames Sent" + << "Frame Send Rate (fps)" + << "Frame Receive Rate (fps)" + << "Bytes Received" + << "Bytes Sent" + << "Byte Send Rate (Bps)" + << "Byte Receive Rate (Bps)" +#if 0 + << "Frames Received (NIC)" + << "Frames Sent (NIC)" + << "Bytes Received (NIC)" + << "Bytes Sent (NIC)" +#endif + << "Receive Drops" + << "Receive Errors" + << "Receive Fifo Errors" + << "Receive Frame Errors" +); + +static QStringList LinkStateName = (QStringList() + << "Unknown" + << "Down" + << "Up" +); + +static QStringList BoolStateName = (QStringList() + << "Off" + << "On" +); + +class PortGroupList; + +class PortStatsModel : public QAbstractTableModel +{ + Q_OBJECT + + public: + + PortStatsModel(PortGroupList *p, QObject *parent = 0); + ~PortStatsModel(); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + + class PortGroupAndPortList { + public: + uint portGroupId; + QList portList; + }; + void portListFromIndex(QModelIndexList indices, + QList &portList); + + public slots: + void when_portListChanged(); + void on_portStatsUpdate(int port, void*stats); + void when_portGroup_stats_update(quint32 portGroupId); + + private slots: + void updateStats(); + + private: + PortGroupList *pgl; + + // numPorts stores the num of ports per portgroup + // in the same order as the portgroups are index in the pgl + // Also it stores them as cumulative totals + QList numPorts; + + QTimer *timer; + + void getDomainIndexes(const QModelIndex &index, + uint &portGroupIdx, uint &portIdx) const; + +}; + +#endif diff --git a/client/portstatswindow.cpp b/client/portstatswindow.cpp new file mode 100644 index 0000000..d82a1fa --- /dev/null +++ b/client/portstatswindow.cpp @@ -0,0 +1,186 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + + +#include "portstatswindow.h" +#include "portstatsmodel.h" +#include "portstatsfilterdialog.h" + +#include "QHeaderView" + +PortStatsWindow::PortStatsWindow(PortGroupList *pgl, QWidget *parent) + : QWidget(parent) +{ + setupUi(this); + + this->pgl = pgl; + model = pgl->getPortStatsModel(); + tvPortStats->setModel(model); + + tvPortStats->verticalHeader()->setHighlightSections(false); + tvPortStats->verticalHeader()->setDefaultSectionSize( + tvPortStats->verticalHeader()->minimumSectionSize()); + +} + +PortStatsWindow::~PortStatsWindow() +{ +} + +/* ------------- SLOTS -------------- */ + +void PortStatsWindow::on_tbStartTransmit_clicked() +{ + QList pgpl; + + // Get selected ports + model->portListFromIndex(tvPortStats->selectionModel()->selectedColumns(), + pgpl); + + // Clear selected ports, portgroup by portgroup + for (int i = 0; i < pgpl.size(); i++) + { + pgl->portGroupByIndex(pgpl.at(i).portGroupId). + startTx(&pgpl[i].portList); + } +} + +void PortStatsWindow::on_tbStopTransmit_clicked() +{ + QList pgpl; + + // Get selected ports + model->portListFromIndex(tvPortStats->selectionModel()->selectedColumns(), + pgpl); + + // Clear selected ports, portgroup by portgroup + for (int i = 0; i < pgpl.size(); i++) + { + pgl->portGroupByIndex(pgpl.at(i).portGroupId). + stopTx(&pgpl[i].portList); + } +} + +void PortStatsWindow::on_tbStartCapture_clicked() +{ + // TODO(MED) + QList pgpl; + + // Get selected ports + model->portListFromIndex(tvPortStats->selectionModel()->selectedColumns(), + pgpl); + + // Clear selected ports, portgroup by portgroup + for (int i = 0; i < pgpl.size(); i++) + { + pgl->portGroupByIndex(pgpl.at(i).portGroupId). + startCapture(&pgpl[i].portList); + } +} + +void PortStatsWindow::on_tbStopCapture_clicked() +{ + // TODO(MED) + QList pgpl; + + // Get selected ports + model->portListFromIndex(tvPortStats->selectionModel()->selectedColumns(), + pgpl); + + // Clear selected ports, portgroup by portgroup + for (int i = 0; i < pgpl.size(); i++) + { + pgl->portGroupByIndex(pgpl.at(i).portGroupId). + stopCapture(&pgpl[i].portList); + } +} + +void PortStatsWindow::on_tbViewCapture_clicked() +{ + // TODO(MED) + QList pgpl; + + // Get selected ports + model->portListFromIndex(tvPortStats->selectionModel()->selectedColumns(), + pgpl); + + // Clear selected ports, portgroup by portgroup + for (int i = 0; i < pgpl.size(); i++) + { + pgl->portGroupByIndex(pgpl.at(i).portGroupId). + viewCapture(&pgpl[i].portList); + } +} + +void PortStatsWindow::on_tbClear_clicked() +{ + QList portList; + + // Get selected ports + model->portListFromIndex(tvPortStats->selectionModel()->selectedColumns(), + portList); + + // Clear selected ports, portgroup by portgroup + for (int i = 0; i < portList.size(); i++) + { + pgl->portGroupByIndex(portList.at(i).portGroupId). + clearPortStats(&portList[i].portList); + } +} + +void PortStatsWindow::on_tbClearAll_clicked() +{ + for (int i = 0; i < pgl->numPortGroups(); i++) + { + pgl->portGroupByIndex(i).clearPortStats(); + } +} + +void PortStatsWindow::on_tbFilter_clicked() +{ + bool ok; + QList currentColumns, newColumns; + PortStatsFilterDialog dialog; + + // create the input list for the filter dialog - + // list of logical-indexes ordered by their current visual indexes + for(int vi = 0; vi < model->columnCount(); vi++) { + int li = tvPortStats->horizontalHeader()->logicalIndex(vi); + if (!tvPortStats->isColumnHidden(li)) { + currentColumns.append(li); + } + } + + // return list from the filter dialog - + // list of logical-indexes ordered by their new visual indexes + newColumns = dialog.getItemList(&ok, model, Qt::Horizontal, currentColumns); + + if (ok) + { + QHeaderView *hv = tvPortStats->horizontalHeader(); + + // hide/show sections first ... + for(int li = 0; li < model->columnCount(); li++) + tvPortStats->setColumnHidden(li, !newColumns.contains(li)); + + // ... then for the 'shown' columns, set the visual index + for(int vi = 0; vi < newColumns.size(); vi++) + hv->moveSection(hv->visualIndex(newColumns.at(vi)), vi); + } +} diff --git a/client/portstatswindow.h b/client/portstatswindow.h new file mode 100644 index 0000000..39f9108 --- /dev/null +++ b/client/portstatswindow.h @@ -0,0 +1,56 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORT_STATS_WINDOW_H +#define _PORT_STATS_WINDOW_H + +#include +#include +#include "ui_portstatswindow.h" +#include "portgrouplist.h" +#include "portstatsmodel.h" + +class PortStatsWindow : public QWidget, public Ui::PortStatsWindow +{ + Q_OBJECT + +public: + PortStatsWindow(PortGroupList *pgl, QWidget *parent = 0); + ~PortStatsWindow(); + +private: + PortGroupList *pgl; + PortStatsModel *model; + +private slots: + void on_tbStartTransmit_clicked(); + void on_tbStopTransmit_clicked(); + + void on_tbStartCapture_clicked(); + void on_tbStopCapture_clicked(); + void on_tbViewCapture_clicked(); + + void on_tbClear_clicked(); + void on_tbClearAll_clicked(); + + void on_tbFilter_clicked(); +}; + +#endif + diff --git a/client/portstatswindow.ui b/client/portstatswindow.ui new file mode 100644 index 0000000..8c6aed4 --- /dev/null +++ b/client/portstatswindow.ui @@ -0,0 +1,182 @@ + + PortStatsWindow + + + + 0 + 0 + 502 + 415 + + + + Form + + + + + + QFrame::Panel + + + QFrame::Raised + + + + + + Start Tx + + + Starts transmit on selected port(s) + + + Start Transmit + + + :/icons/control_play.png + + + + + + + Stop Tx + + + Stops transmit on selected port(s) + + + Stop Trasmit + + + :/icons/control_stop.png + + + + + + + Clear Selected Port Stats + + + Clears statistics of the selected port(s) + + + Clear + + + :/icons/portstats_clear.png + + + + + + + Clear All Ports Stats + + + Clears statistics of all ports + + + Clear All + + + :/icons/portstats_clear_all.png + + + + + + + Start Capture + + + Captures packets on the selected port(s) + + + Start Capture + + + :/icons/sound_none.png + + + + + + + Stop Capture + + + End capture on selecteed port(s) + + + Stop Capture + + + :/icons/sound_mute.png + + + + + + + View Capture Buffer + + + View captured packets on selected port(s) + + + View Capture + + + :/icons/magnifier.png + + + + + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Select which ports to view + + + Filter + + + :/icons/portstats_filter.png + + + + + + + + + + + + + + + + diff --git a/client/portswindow.cpp b/client/portswindow.cpp new file mode 100644 index 0000000..83fdc75 --- /dev/null +++ b/client/portswindow.cpp @@ -0,0 +1,692 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "portswindow.h" + +#include "abstractfileformat.h" +#include "portconfigdialog.h" +#include "streamconfigdialog.h" +#include "streamlistdelegate.h" + +#include +#include +#include +#include + +PortsWindow::PortsWindow(PortGroupList *pgl, QWidget *parent) + : QWidget(parent) +{ + QAction *sep; + + delegate = new StreamListDelegate; + //slm = new StreamListModel(); + //plm = new PortGroupList(); + plm = pgl; + + setupUi(this); + + tvPortList->header()->hide(); + + tvStreamList->setItemDelegate(delegate); + + tvStreamList->verticalHeader()->setDefaultSectionSize( + tvStreamList->verticalHeader()->minimumSectionSize()); + + // Populate PortList Context Menu Actions + tvPortList->addAction(actionNew_Port_Group); + tvPortList->addAction(actionDelete_Port_Group); + tvPortList->addAction(actionConnect_Port_Group); + tvPortList->addAction(actionDisconnect_Port_Group); + + tvPortList->addAction(actionExclusive_Control); + tvPortList->addAction(actionPort_Configuration); + + // Populate StramList Context Menu Actions + tvStreamList->addAction(actionNew_Stream); + tvStreamList->addAction(actionEdit_Stream); + tvStreamList->addAction(actionDuplicate_Stream); + tvStreamList->addAction(actionDelete_Stream); + + sep = new QAction(this); + sep->setSeparator(true); + tvStreamList->addAction(sep); + + tvStreamList->addAction(actionOpen_Streams); + tvStreamList->addAction(actionSave_Streams); + + // PortList and StreamList actions combined make this window's actions + addActions(tvPortList->actions()); + sep = new QAction(this); + sep->setSeparator(true); + addAction(sep); + addActions(tvStreamList->actions()); + + tvStreamList->setModel(plm->getStreamModel()); + tvPortList->setModel(plm->getPortModel()); + + connect( plm->getPortModel(), + SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(when_portModel_dataChanged(const QModelIndex&, + const QModelIndex&))); + + connect(plm->getPortModel(), SIGNAL(modelReset()), + SLOT(when_portModel_reset())); + + connect( tvPortList->selectionModel(), + SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(when_portView_currentChanged(const QModelIndex&, + const QModelIndex&))); + + connect(plm->getStreamModel(), SIGNAL(rowsInserted(QModelIndex, int, int)), + SLOT(updateStreamViewActions())); + connect(plm->getStreamModel(), SIGNAL(rowsRemoved(QModelIndex, int, int)), + SLOT(updateStreamViewActions())); + + connect(tvStreamList->selectionModel(), + SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + SLOT(updateStreamViewActions())); + connect(tvStreamList->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + SLOT(updateStreamViewActions())); + + tvStreamList->resizeColumnToContents(StreamModel::StreamIcon); + tvStreamList->resizeColumnToContents(StreamModel::StreamStatus); + + // Initially we don't have any ports/streams - so send signal triggers + when_portView_currentChanged(QModelIndex(), QModelIndex()); + updateStreamViewActions(); + + connect(plm->getStreamModel(), + SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(streamModelDataChanged())); + connect(plm->getStreamModel(), + SIGNAL(modelReset()), + this, SLOT(streamModelDataChanged())); +} + +void PortsWindow::streamModelDataChanged() +{ + if (plm->isPort(tvPortList->currentIndex())) + plm->port(tvPortList->currentIndex()).recalculateAverageRates(); +} + +PortsWindow::~PortsWindow() +{ + delete delegate; +} + +void PortsWindow::on_tvStreamList_activated(const QModelIndex & index) +{ + StreamConfigDialog *scd; + int ret; + + if (!index.isValid()) + { + qDebug("%s: invalid index", __FUNCTION__); + return; + } + scd = new StreamConfigDialog(plm->port(tvPortList->currentIndex()), + index.row(), this); + qDebug("stream list activated\n"); + ret = scd->exec(); + + if (ret == QDialog::Accepted) + plm->port(tvPortList->currentIndex()).recalculateAverageRates(); + + delete scd; +} + +void PortsWindow::when_portView_currentChanged(const QModelIndex& current, + const QModelIndex& previous) +{ + plm->getStreamModel()->setCurrentPortIndex(current); + updatePortViewActions(current); + updateStreamViewActions(); + + qDebug("In %s", __FUNCTION__); + + if (previous.isValid() && plm->isPort(previous)) + { + disconnect(&(plm->port(previous)), SIGNAL(portRateChanged(int, int)), + this, SLOT(updatePortRates())); + } + + if (!current.isValid()) + { + qDebug("setting stacked widget to blank page"); + swDetail->setCurrentIndex(2); // blank page + } + else + { + if (plm->isPortGroup(current)) + { + swDetail->setCurrentIndex(1); // portGroup detail page + } + else if (plm->isPort(current)) + { + swDetail->setCurrentIndex(0); // port detail page + updatePortRates(); + connect(&(plm->port(current)), SIGNAL(portRateChanged(int, int)), + SLOT(updatePortRates())); + } + } +} + +void PortsWindow::when_portModel_dataChanged(const QModelIndex& topLeft, + const QModelIndex& bottomRight) +{ + qDebug("In %s", __FUNCTION__); +#if 0 // not sure why the >= <= operators are not overloaded in QModelIndex + if ((tvPortList->currentIndex() >= topLeft) && + (tvPortList->currentIndex() <= bottomRight)) +#endif + if (((topLeft < tvPortList->currentIndex()) || + (topLeft == tvPortList->currentIndex())) && + (((tvPortList->currentIndex() < bottomRight)) || + (tvPortList->currentIndex() == bottomRight))) + { + // Update UI to reflect potential change in exclusive mode, + // transmit mode et al + when_portView_currentChanged(tvPortList->currentIndex(), + tvPortList->currentIndex()); + } +} + +void PortsWindow::when_portModel_reset() +{ + when_portView_currentChanged(QModelIndex(), tvPortList->currentIndex()); +} + +void PortsWindow::on_averagePacketsPerSec_editingFinished() +{ + QModelIndex current = tvPortList->currentIndex(); + + Q_ASSERT(plm->isPort(current)); + + bool isOk; + double pps = QLocale().toDouble(averagePacketsPerSec->text(), &isOk); + + plm->port(current).setAveragePacketRate(pps); +} + +void PortsWindow::on_averageBitsPerSec_editingFinished() +{ + QModelIndex current = tvPortList->currentIndex(); + + Q_ASSERT(plm->isPort(current)); + + bool isOk; + double bps = QLocale().toDouble(averageBitsPerSec->text(), &isOk); + + plm->port(current).setAverageBitRate(bps); +} + +void PortsWindow::updatePortRates() +{ + QModelIndex current = tvPortList->currentIndex(); + + if (!current.isValid()) + return; + + if (!plm->isPort(current)) + return; + + averagePacketsPerSec->setText(QString("%L1") + .arg(plm->port(current).averagePacketRate(), 0, 'f', 4)); + averageBitsPerSec->setText(QString("%L1") + .arg(plm->port(current).averageBitRate(), 0, 'f', 0)); +} + +void PortsWindow::updateStreamViewActions() +{ + // For some reason hasSelection() returns true even if selection size is 0 + // so additional check for size introduced + if (tvStreamList->selectionModel()->hasSelection() && + (tvStreamList->selectionModel()->selection().size() > 0)) + { + qDebug("Has selection %d", + tvStreamList->selectionModel()->selection().size()); + + // If more than one non-contiguous ranges selected, + // disable "New" and "Edit" + if (tvStreamList->selectionModel()->selection().size() > 1) + { + actionNew_Stream->setDisabled(true); + actionEdit_Stream->setDisabled(true); + } + else + { + actionNew_Stream->setEnabled(true); + + // Enable "Edit" only if the single range has a single row + if (tvStreamList->selectionModel()->selection().at(0).height() > 1) + actionEdit_Stream->setDisabled(true); + else + actionEdit_Stream->setEnabled(true); + } + + // Duplicate/Delete are always enabled as long as we have a selection + actionDuplicate_Stream->setEnabled(true); + actionDelete_Stream->setEnabled(true); + } + else + { + qDebug("No selection"); + if (plm->isPort(tvPortList->currentIndex())) + actionNew_Stream->setEnabled(true); + else + actionNew_Stream->setDisabled(true); + actionEdit_Stream->setDisabled(true); + actionDuplicate_Stream->setDisabled(true); + actionDelete_Stream->setDisabled(true); + } + actionOpen_Streams->setEnabled(plm->isPort( + tvPortList->selectionModel()->currentIndex())); + actionSave_Streams->setEnabled(tvStreamList->model()->rowCount() > 0); +} + +void PortsWindow::updatePortViewActions(const QModelIndex& current) +{ + if (!current.isValid()) + { + qDebug("current is now invalid"); + actionDelete_Port_Group->setDisabled(true); + actionConnect_Port_Group->setDisabled(true); + actionDisconnect_Port_Group->setDisabled(true); + + actionExclusive_Control->setDisabled(true); + actionPort_Configuration->setDisabled(true); + + goto _EXIT; + } + + qDebug("currentChanged %llx", current.internalId()); + + if (plm->isPortGroup(current)) + { + actionDelete_Port_Group->setEnabled(true); + + actionExclusive_Control->setDisabled(true); + actionPort_Configuration->setDisabled(true); + + switch(plm->portGroup(current).state()) + { + case QAbstractSocket::UnconnectedState: + case QAbstractSocket::ClosingState: + qDebug("state = unconnected|closing"); + actionConnect_Port_Group->setEnabled(true); + actionDisconnect_Port_Group->setDisabled(true); + break; + + case QAbstractSocket::HostLookupState: + case QAbstractSocket::ConnectingState: + case QAbstractSocket::ConnectedState: + qDebug("state = lookup|connecting|connected"); + actionConnect_Port_Group->setDisabled(true); + actionDisconnect_Port_Group->setEnabled(true); + break; + + + case QAbstractSocket::BoundState: + case QAbstractSocket::ListeningState: + default: + // FIXME(LOW): indicate error + qDebug("unexpected state"); + break; + } + } + else if (plm->isPort(current)) + { + actionDelete_Port_Group->setDisabled(true); + actionConnect_Port_Group->setDisabled(true); + actionDisconnect_Port_Group->setDisabled(true); + + actionExclusive_Control->setEnabled(true); + if (plm->port(current).hasExclusiveControl()) + actionExclusive_Control->setChecked(true); + else + actionExclusive_Control->setChecked(false); + actionPort_Configuration->setEnabled(true); + } + +_EXIT: + return; +} + +void PortsWindow::on_pbApply_clicked() +{ + QModelIndex curPort; + QModelIndex curPortGroup; + + curPort = tvPortList->selectionModel()->currentIndex(); + if (!curPort.isValid()) + { + qDebug("%s: curPort is invalid", __FUNCTION__); + goto _exit; + } + + if (!plm->isPort(curPort)) + { + qDebug("%s: curPort is not a port", __FUNCTION__); + goto _exit; + } + + if (plm->port(curPort).getStats().state().is_transmit_on()) + { + QMessageBox::information(0, "Configuration Change", + "Please stop transmit on the port before applying any changes"); + goto _exit; + } + + curPortGroup = plm->getPortModel()->parent(curPort); + if (!curPortGroup.isValid()) + { + qDebug("%s: curPortGroup is invalid", __FUNCTION__); + goto _exit; + } + if (!plm->isPortGroup(curPortGroup)) + { + qDebug("%s: curPortGroup is not a portGroup", __FUNCTION__); + goto _exit; + } + + // FIXME(HI): shd this be a signal? + //portGroup.when_configApply(port); + // FIXME(MED): mixing port id and index!!! + plm->portGroup(curPortGroup).when_configApply(plm->port(curPort).id()); + +_exit: + return; + +#if 0 + // TODO (LOW): This block is for testing only + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (current.isValid()) + qDebug("current = %llx", current.internalId()); + else + qDebug("current is invalid"); +#endif +} + +void PortsWindow::on_actionNew_Port_Group_triggered() +{ + bool ok; + QString text = QInputDialog::getText(this, + "Add Port Group", "Port Group Address (IP[:Port])", + QLineEdit::Normal, lastNewPortGroup, &ok); + + if (ok) + { + QStringList addr = text.split(":"); + if (addr.size() == 1) // Port unspecified + addr.append(QString().setNum(DEFAULT_SERVER_PORT)); + PortGroup *pg = new PortGroup(QHostAddress(addr[0]),addr[1].toUShort()); + plm->addPortGroup(*pg); + lastNewPortGroup = text; + } +} + +void PortsWindow::on_actionDelete_Port_Group_triggered() +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (current.isValid()) + plm->removePortGroup(plm->portGroup(current)); +} + +void PortsWindow::on_actionConnect_Port_Group_triggered() +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (current.isValid()) + plm->portGroup(current).connectToHost(); +} + +void PortsWindow::on_actionDisconnect_Port_Group_triggered() +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (current.isValid()) + plm->portGroup(current).disconnectFromHost(); +} + +void PortsWindow::on_actionExclusive_Control_triggered(bool checked) +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (plm->isPort(current)) + { + OstProto::Port config; + + config.set_is_exclusive_control(checked); + plm->portGroup(current.parent()).modifyPort(current.row(), config); + } +} + +void PortsWindow::on_actionPort_Configuration_triggered() +{ + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + + if (!plm->isPort(current)) + return; + + OstProto::Port config; + config.set_transmit_mode(plm->port(current).transmitMode()); + config.set_is_exclusive_control(plm->port(current).hasExclusiveControl()); + config.set_user_name(plm->port(current).userName().toStdString()); + + PortConfigDialog dialog(config, this); + + if (dialog.exec() == QDialog::Accepted) + plm->portGroup(current.parent()).modifyPort(current.row(), config); +} + +void PortsWindow::on_actionNew_Stream_triggered() +{ + qDebug("New Stream Action"); + + // In case nothing is selected, insert 1 row at the top + int row = 0, count = 1; + + // In case we have a single range selected; insert as many rows as + // in the singe selected range before the top of the selected range + if (tvStreamList->selectionModel()->selection().size() == 1) + { + row = tvStreamList->selectionModel()->selection().at(0).top(); + count = tvStreamList->selectionModel()->selection().at(0).height(); + } + + plm->getStreamModel()->insertRows(row, count); +} + +void PortsWindow::on_actionEdit_Stream_triggered() +{ + qDebug("Edit Stream Action"); + + // Ensure we have only one range selected which contains only one row + if ((tvStreamList->selectionModel()->selection().size() == 1) && + (tvStreamList->selectionModel()->selection().at(0).height() == 1)) + { + on_tvStreamList_activated(tvStreamList->selectionModel()-> + selection().at(0).topLeft()); + } +} + +void PortsWindow::on_actionDuplicate_Stream_triggered() +{ + QItemSelectionModel* model = tvStreamList->selectionModel(); + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + qDebug("Duplicate Stream Action"); + + if (model->hasSelection()) + { + bool isOk; + int count = QInputDialog::getInteger(this, "Duplicate Streams", + "Count", 1, 1, 9999, 1, &isOk); + + if (!isOk) + return; + + QList list; + foreach(QModelIndex index, model->selectedRows()) + list.append(index.row()); + plm->port(current).duplicateStreams(list, count); + } + else + qDebug("No selection"); +} + +void PortsWindow::on_actionDelete_Stream_triggered() +{ + qDebug("Delete Stream Action"); + + QModelIndex index; + + if (tvStreamList->selectionModel()->hasSelection()) + { + qDebug("SelectedIndexes %d", + tvStreamList->selectionModel()->selectedRows().size()); + while(tvStreamList->selectionModel()->selectedRows().size()) + { + index = tvStreamList->selectionModel()->selectedRows().at(0); + plm->getStreamModel()->removeRows(index.row(), 1); + } + } + else + qDebug("No selection"); +} + +void PortsWindow::on_actionOpen_Streams_triggered() +{ + qDebug("Open Streams Action"); + + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + static QString dirName; + QString fileName; + QString errorStr; + bool append = true; + bool ret; + + Q_ASSERT(plm->isPort(current)); + + fileName = QFileDialog::getOpenFileName(this, tr("Open Streams"), dirName); + if (fileName.isEmpty()) + goto _exit; + + if (tvStreamList->model()->rowCount()) + { + QMessageBox msgBox(QMessageBox::Question, qApp->applicationName(), + tr("Append to existing streams? Or overwrite?"), + QMessageBox::NoButton, this); + QPushButton *appendBtn = msgBox.addButton(tr("Append"), + QMessageBox::ActionRole); + QPushButton *overwriteBtn = msgBox.addButton(tr("Overwrite"), + QMessageBox::ActionRole); + QPushButton *cancelBtn = msgBox.addButton(QMessageBox::Cancel); + + msgBox.exec(); + + if (msgBox.clickedButton() == cancelBtn) + goto _exit; + else if (msgBox.clickedButton() == appendBtn) + append = true; + else if (msgBox.clickedButton() == overwriteBtn) + append = false; + else + Q_ASSERT(false); + } + + ret = plm->port(current).openStreams(fileName, append, errorStr); + if (!ret || !errorStr.isEmpty()) + { + QMessageBox msgBox(this); + QStringList str = errorStr.split("\n\n\n\n"); + + msgBox.setIcon(ret ? QMessageBox::Warning : QMessageBox::Critical); + msgBox.setWindowTitle(qApp->applicationName()); + msgBox.setText(str.at(0)); + if (str.size() > 1) + msgBox.setDetailedText(str.at(1)); + msgBox.setStandardButtons(QMessageBox::Ok); + + msgBox.exec(); + } + dirName = QFileInfo(fileName).absolutePath(); + +_exit: + return; +} + +void PortsWindow::on_actionSave_Streams_triggered() +{ + qDebug("Save Streams Action"); + + QModelIndex current = tvPortList->selectionModel()->currentIndex(); + static QString fileName; + QStringList fileTypes = AbstractFileFormat::supportedFileTypes(); + QString fileType; + QString errorStr; + QFileDialog::Options options; + + // On Mac OS with Native Dialog, getSaveFileName() ignores fileType + // which we need.On some Linux distros the native dialog can't + // distinguish between Ostinato(*) and PCAP(*) +#if defined(Q_OS_MAC) || defined(Q_OS_UNIX) + options |= QFileDialog::DontUseNativeDialog; +#endif + + if (fileTypes.size()) + fileType = fileTypes.at(0); + + Q_ASSERT(plm->isPort(current)); + +_retry: + fileName = QFileDialog::getSaveFileName(this, tr("Save Streams"), + fileName, fileTypes.join(";;"), &fileType, options); + if (fileName.isEmpty()) + goto _exit; + + fileType = fileType.remove(QRegExp("\\(.*\\)")).trimmed(); + if (!fileType.startsWith("Ostinato") + && !fileType.startsWith("Python")) + { + if (QMessageBox::warning(this, tr("Ostinato"), + QString("You have chosen to save in %1 format. All stream " + "attributes may not be saved in this format.\n\n" + "It is recommended to save in native Ostinato format.\n\n" + "Continue to save in %2 format?").arg(fileType).arg(fileType), + QMessageBox::Yes|QMessageBox::No, + QMessageBox::No) != QMessageBox::Yes) + goto _retry; + } + + // TODO: all or selected? + + if (!plm->port(current).saveStreams(fileName, fileType, errorStr)) + QMessageBox::critical(this, qApp->applicationName(), errorStr); + else if (!errorStr.isEmpty()) + QMessageBox::warning(this, qApp->applicationName(), errorStr); + + fileName = QFileInfo(fileName).absolutePath(); +_exit: + return; +} + + diff --git a/client/portswindow.h b/client/portswindow.h new file mode 100644 index 0000000..98338e9 --- /dev/null +++ b/client/portswindow.h @@ -0,0 +1,87 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PORTS_WINDOW_H +#define _PORTS_WINDOW_H + +#include +#include +#include "ui_portswindow.h" +#include "portgrouplist.h" + +/* TODO +HIGH +MED +LOW +*/ + +class QAbstractItemDelegate; + +class PortsWindow : public QWidget, private Ui::PortsWindow +{ + Q_OBJECT + + //QAbstractItemModel *slm; // stream list model + PortGroupList *plm; + +public: + PortsWindow(PortGroupList *pgl, QWidget *parent = 0); + ~PortsWindow(); + +private: + QString lastNewPortGroup; + QAbstractItemDelegate *delegate; + +private slots: + void updatePortViewActions(const QModelIndex& current); + void updateStreamViewActions(); + + void on_averagePacketsPerSec_editingFinished(); + void on_averageBitsPerSec_editingFinished(); + void updatePortRates(); + void on_tvStreamList_activated(const QModelIndex & index); + void when_portView_currentChanged(const QModelIndex& current, + const QModelIndex& previous); + void when_portModel_dataChanged(const QModelIndex& topLeft, + const QModelIndex& bottomRight); + void when_portModel_reset(); + + void on_pbApply_clicked(); + + void on_actionNew_Port_Group_triggered(); + void on_actionDelete_Port_Group_triggered(); + void on_actionConnect_Port_Group_triggered(); + void on_actionDisconnect_Port_Group_triggered(); + + void on_actionExclusive_Control_triggered(bool checked); + void on_actionPort_Configuration_triggered(); + + void on_actionNew_Stream_triggered(); + void on_actionEdit_Stream_triggered(); + void on_actionDuplicate_Stream_triggered(); + void on_actionDelete_Stream_triggered(); + + void on_actionOpen_Streams_triggered(); + void on_actionSave_Streams_triggered(); + + void streamModelDataChanged(); +}; + +#endif + diff --git a/client/portswindow.ui b/client/portswindow.ui new file mode 100644 index 0000000..d3a2c55 --- /dev/null +++ b/client/portswindow.ui @@ -0,0 +1,307 @@ + + PortsWindow + + + + 0 + 0 + 710 + 352 + + + + Form + + + + + + Qt::Horizontal + + + false + + + + Qt::ActionsContextMenu + + + QAbstractItemView::SingleSelection + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + Avg pps + + + true + + + + + + + + + + Avg bps + + + + + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + Apply + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + 0 + 1 + + + + Qt::ActionsContextMenu + + + QFrame::StyledPanel + + + 1 + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + + + + + + + Select a port to configure streams + + + Qt::AlignCenter + + + + + + + + + + + + + :/icons/portgroup_add.png + + + New Port Group + + + + + :/icons/portgroup_delete.png + + + Delete Port Group + + + + + :/icons/portgroup_connect.png + + + Connect Port Group + + + + + :/icons/portgroup_disconnect.png + + + Disconnect Port Group + + + + + :/icons/stream_add.png + + + New Stream + + + + + :/icons/stream_delete.png + + + Delete Stream + + + + + :/icons/stream_edit.png + + + Edit Stream + + + + + true + + + Exclusive Port Control (EXPERIMENTAL) + + + + + Open Streams ... + + + + + Save Streams ... + + + + + Port Configuration ... + + + + + :/icons/stream_duplicate.png + + + Duplicate Stream + + + + + + + + + radioButton + toggled(bool) + averagePacketsPerSec + setEnabled(bool) + + + 313 + 28 + + + 380 + 28 + + + + + radioButton_2 + toggled(bool) + averageBitsPerSec + setEnabled(bool) + + + 333 + 55 + + + 395 + 56 + + + + + diff --git a/client/preferences.cpp b/client/preferences.cpp new file mode 100644 index 0000000..cbd9a58 --- /dev/null +++ b/client/preferences.cpp @@ -0,0 +1,137 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "preferences.h" + +#include "../common/ostprotolib.h" +#include "settings.h" + +#include +#include + +QString kUserDefaultValue; + +Preferences::Preferences() +{ + Q_ASSERT(appSettings); + + setupUi(this); + + wiresharkPathEdit->setText(appSettings->value(kWiresharkPathKey, + kWiresharkPathDefaultValue).toString()); + tsharkPathEdit->setText(appSettings->value(kTsharkPathKey, + kTsharkPathDefaultValue).toString()); + gzipPathEdit->setText(appSettings->value(kGzipPathKey, + kGzipPathDefaultValue).toString()); + diffPathEdit->setText(appSettings->value(kDiffPathKey, + kDiffPathDefaultValue).toString()); + awkPathEdit->setText(appSettings->value(kAwkPathKey, + kAwkPathDefaultValue).toString()); + + // TODO(only if required): kUserKey +} + +Preferences::~Preferences() +{ +} + +void Preferences::initDefaults() +{ + Q_ASSERT(appSettings); + + // Read username from the environment +#ifdef Q_OS_WIN32 + kUserDefaultValue = QString(qgetenv("USERNAME").constData()); +#else + kUserDefaultValue = QString(qgetenv("USER").constData()); +#endif + qDebug("current user <%s>", qPrintable(kUserDefaultValue)); +} + +void Preferences::accept() +{ + appSettings->setValue(kWiresharkPathKey, wiresharkPathEdit->text()); + appSettings->setValue(kTsharkPathKey, tsharkPathEdit->text()); + appSettings->setValue(kGzipPathKey, gzipPathEdit->text()); + appSettings->setValue(kDiffPathKey, diffPathEdit->text()); + appSettings->setValue(kAwkPathKey, awkPathEdit->text()); + + OstProtoLib::setExternalApplicationPaths( + appSettings->value(kTsharkPathKey, kTsharkPathDefaultValue).toString(), + appSettings->value(kGzipPathKey, kGzipPathDefaultValue).toString(), + appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(), + appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); + + QDialog::accept(); +} + +void Preferences::on_wiresharkPathButton_clicked() +{ + QString path; + + path = QFileDialog::getOpenFileName(0, "Locate Wireshark", + wiresharkPathEdit->text()); + + if (!path.isEmpty()) + wiresharkPathEdit->setText(path); +} + +void Preferences::on_tsharkPathButton_clicked() +{ + QString path; + + path = QFileDialog::getOpenFileName(0, "Locate tshark", + tsharkPathEdit->text()); + + if (!path.isEmpty()) + tsharkPathEdit->setText(path); +} + +void Preferences::on_gzipPathButton_clicked() +{ + QString path; + + path = QFileDialog::getOpenFileName(0, "Locate gzip", + gzipPathEdit->text()); + + if (!path.isEmpty()) + gzipPathEdit->setText(path); +} + +void Preferences::on_diffPathButton_clicked() +{ + QString path; + + path = QFileDialog::getOpenFileName(0, "Locate diff", + diffPathEdit->text()); + + if (!path.isEmpty()) + diffPathEdit->setText(path); +} + +void Preferences::on_awkPathButton_clicked() +{ + QString path; + + path = QFileDialog::getOpenFileName(0, "Locate awk", + awkPathEdit->text()); + + if (!path.isEmpty()) + awkPathEdit->setText(path); +} diff --git a/client/preferences.h b/client/preferences.h new file mode 100644 index 0000000..dfb3ee0 --- /dev/null +++ b/client/preferences.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PREFERENCES_H +#define _PREFERENCES_H + +#include "ui_preferences.h" + +#include + +class Preferences : public QDialog, private Ui::Preferences +{ + Q_OBJECT +public: + Preferences(); + ~Preferences(); + + static void initDefaults(); + +public slots: + void accept(); + +private slots: + void on_wiresharkPathButton_clicked(); + void on_tsharkPathButton_clicked(); + void on_gzipPathButton_clicked(); + void on_diffPathButton_clicked(); + void on_awkPathButton_clicked(); +}; + +#endif diff --git a/client/preferences.ui b/client/preferences.ui new file mode 100644 index 0000000..d64b4cb --- /dev/null +++ b/client/preferences.ui @@ -0,0 +1,226 @@ + + Preferences + + + + 0 + 0 + 400 + 220 + + + + Preferences + + + :/icons/preferences.png + + + + + + QFrame::Box + + + QFrame::Sunken + + + + + + 'wireshark' Path + + + wiresharkPathEdit + + + + + + + false + + + + + + + ... + + + + + + + 'tshark' Path + + + tsharkPathEdit + + + + + + + false + + + + + + + ... + + + + + + + 'gzip' Path + + + diffPathEdit + + + + + + + false + + + + + + + ... + + + + + + + 'diff' Path + + + diffPathEdit + + + + + + + false + + + + + + + ... + + + + + + + 'awk' Path + + + awkPathEdit + + + + + + + false + + + + + + + ... + + + + + + + Qt::Vertical + + + + 21 + 61 + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + wiresharkPathEdit + wiresharkPathButton + tsharkPathEdit + tsharkPathButton + gzipPathEdit + gzipPathButton + diffPathEdit + diffPathButton + awkPathEdit + awkPathButton + buttonBox + + + + + + + buttonBox + accepted() + Preferences + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Preferences + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/client/settings.h b/client/settings.h new file mode 100644 index 0000000..8324744 --- /dev/null +++ b/client/settings.h @@ -0,0 +1,88 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SETTINGS_H +#define _SETTINGS_H + +#include +#include + +extern QSettings *appSettings; + +const QString kWiresharkPathKey("WiresharkPath"); +#if defined(Q_OS_WIN32) +const QString kWiresharkPathDefaultValue( + "C:/Program Files/Wireshark/wireshark.exe"); +#elif defined(Q_OS_MAC) +const QString kWiresharkPathDefaultValue( + "/Applications/Wireshark.app/Contents/Resources/bin/wireshark"); +#else +const QString kWiresharkPathDefaultValue("/usr/bin/wireshark"); +#endif + +const QString kTsharkPathKey("TsharkPath"); +#if defined(Q_OS_WIN32) +const QString kTsharkPathDefaultValue( + "C:/Program Files/Wireshark/tshark.exe"); +#elif defined(Q_OS_MAC) +const QString kTsharkPathDefaultValue( + "/Applications/Wireshark.app/Contents/Resources/bin/tshark"); +#else +const QString kTsharkPathDefaultValue("/usr/bin/tshark"); +#endif + +const QString kGzipPathKey("GzipPath"); +#if defined(Q_OS_WIN32) +extern QString kGzipPathDefaultValue; +#elif defined(Q_OS_MAC) +const QString kGzipPathDefaultValue("/usr/bin/gzip"); +#else +const QString kGzipPathDefaultValue("/usr/bin/gzip"); +#endif + +const QString kDiffPathKey("DiffPath"); +#if defined(Q_OS_WIN32) +extern QString kDiffPathDefaultValue; +#elif defined(Q_OS_MAC) +const QString kDiffPathDefaultValue("/usr/bin/diff"); +#else +const QString kDiffPathDefaultValue("/usr/bin/diff"); +#endif + +const QString kAwkPathKey("AwkPath"); +#if defined(Q_OS_WIN32) +extern QString kAwkPathDefaultValue; +#elif defined(Q_OS_MAC) +const QString kAwkPathDefaultValue("/usr/bin/awk"); +#else +const QString kAwkPathDefaultValue("/usr/bin/awk"); +#endif + +const QString kUserKey("User"); +extern QString kUserDefaultValue; + +// +// LastUse Section Keys +// +const QString kApplicationWindowGeometryKey("LastUse/ApplicationWindowGeometry"); +const QString kApplicationWindowLayout("LastUse/ApplicationWindowLayout"); + +#endif + + diff --git a/client/stream.cpp b/client/stream.cpp new file mode 100644 index 0000000..2305930 --- /dev/null +++ b/client/stream.cpp @@ -0,0 +1,51 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +/*! + * \todo Remove this class + */ + +#include +#include + +#include "stream.h" +#include "../common/protocollistiterator.h" +#include "../common/abstractprotocol.h" + +Stream::Stream() +{ + //mId = 0xFFFFFFFF; + setEnabled(true); +} + +Stream::~Stream() +{ +} + +void Stream::loadProtocolWidgets() +{ + qWarning("%s: DOES NOTHING", __PRETTY_FUNCTION__); + return; +} + +void Stream::storeProtocolWidgets() +{ + qWarning("%s: DOES NOTHING", __PRETTY_FUNCTION__); + return; +} diff --git a/client/stream.h b/client/stream.h new file mode 100644 index 0000000..213af70 --- /dev/null +++ b/client/stream.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _STREAM_H +#define _STREAM_H + +#include +#include +#include + +#include "../common/protocol.pb.h" +#include "../common/streambase.h" + +class Stream : public StreamBase { + + //quint32 mId; + +public: + Stream(); + ~Stream(); + + void loadProtocolWidgets(); + void storeProtocolWidgets(); +}; + +#endif diff --git a/client/streamconfigdialog.cpp b/client/streamconfigdialog.cpp new file mode 100644 index 0000000..32a4ca7 --- /dev/null +++ b/client/streamconfigdialog.cpp @@ -0,0 +1,1212 @@ +/* +Copyright (C) 2010-2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include + +#include "streamconfigdialog.h" +#include "stream.h" +#include "abstractprotocol.h" +#include "abstractprotocolconfig.h" +#include "protocollistiterator.h" + +#include "modeltest.h" + +#include "../common/protocolmanager.h" +#include "../common/protocolwidgetfactory.h" + +extern ProtocolManager *OstProtocolManager; +extern ProtocolWidgetFactory *OstProtocolWidgetFactory; + +QRect StreamConfigDialog::lastGeometry; +int StreamConfigDialog::lastTopLevelTabIndex = 0; +int StreamConfigDialog::lastProtocolDataIndex = 0; + +static const uint kEthFrameOverHead = 20; + +StreamConfigDialog::StreamConfigDialog(Port &port, uint streamIndex, + QWidget *parent) : QDialog (parent), mPort(port) +{ + OstProto::Stream s; + mCurrentStreamIndex = streamIndex; + mpStream = new Stream; + mPort.streamByIndex(mCurrentStreamIndex)->protoDataCopyInto(s); + mpStream->protoDataCopyFrom(s); + _iter = mpStream->createProtocolListIterator(); + isUpdateInProgress = false; + + setupUi(this); + setupUiExtra(); + + for (int i = ProtoMin; i < ProtoMax; i++) + { + bgProto[i]->setProperty("ProtocolLevel", i); + bgProto[i]->setProperty("ProtocolId", ButtonIdNone); + connect(bgProto[i], SIGNAL(buttonClicked(int)), + this, SLOT(updateProtocol(int))); + } + + //! \todo causes a crash! +#if 0 + connect(lePktLen, SIGNAL(textEdited(QString)), + this, SLOT(updateContents())); +#endif + + // Time to play match the signals and slots! + + // If L1/L2(FT)/L3 = None, force subsequent protocol level(s) also to None + connect(rbL1None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool))); + connect(rbFtNone, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool))); + connect(rbL3None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool))); + connect(rbL4None, SIGNAL(toggled(bool)), SLOT(forceProtocolNone(bool))); + + // If L1/L2(FT)/L3/L4 = Other, force subsequent protocol to Other and + // disable the subsequent protocol group as well + connect(rbL1Other, SIGNAL(toggled(bool)), rbFtOther, SLOT(setChecked(bool))); + connect(rbL1Other, SIGNAL(toggled(bool)), gbFrameType, SLOT(setDisabled(bool))); + connect(rbFtOther, SIGNAL(toggled(bool)), rbL3Other, SLOT(setChecked(bool))); + connect(rbFtOther, SIGNAL(toggled(bool)), gbL3Proto, SLOT(setDisabled(bool))); + connect(rbL3Other, SIGNAL(toggled(bool)), rbL4Other, SLOT(setChecked(bool))); + connect(rbL3Other, SIGNAL(toggled(bool)), gbL4Proto, SLOT(setDisabled(bool))); + connect(rbL4Other, SIGNAL(toggled(bool)), rbPayloadOther, SLOT(setChecked(bool))); + connect(rbL4Other, SIGNAL(toggled(bool)), gbPayloadProto, SLOT(setDisabled(bool))); + + // Setup valid subsequent protocols for L2 to L4 protocols + for (int i = ProtoL2; i <= ProtoL4; i++) + { + foreach(QAbstractButton *btn1, bgProto[i]->buttons()) + { + int id1 = bgProto[i]->id(btn1); + + if (id1 != ButtonIdNone && id1 != ButtonIdOther) + { + int validProtocolCount = 0; + + foreach(QAbstractButton *btn2, bgProto[i+1]->buttons()) + { + int id2 = bgProto[i+1]->id(btn2); + + if (id2 != ButtonIdNone && id2 != ButtonIdOther) + { + if (OstProtocolManager->isValidNeighbour(id1, id2)) + { + connect(btn1, SIGNAL(toggled(bool)), + btn2, SLOT(setEnabled(bool))); + validProtocolCount++; + } + else + connect(btn1, SIGNAL(toggled(bool)), + btn2, SLOT(setDisabled(bool))); + } + } + + // If btn1 has no subsequent valid protocols, + // force subsequent Protocol to 'None' + if (validProtocolCount == 0) + connect(btn1, SIGNAL(clicked(bool)), + bgProto[i+1]->button(ButtonIdNone), SLOT(click())); + + // If the id1 protocol doesn't have a payload (e.g. IGMP) + // force payload protocol to 'None' + if (!OstProtocolManager->protocolHasPayload(id1)) + { + connect(btn1, SIGNAL(clicked(bool)), + bgProto[ProtoPayload]->button(ButtonIdNone), + SLOT(click())); + } + } + } + } + + mpAvailableProtocolsModel = new QStringListModel( + OstProtocolManager->protocolDatabase(), this); + lvAllProtocols->setModel(mpAvailableProtocolsModel); + lvAllProtocols->setEditTriggers(QAbstractItemView::NoEditTriggers); + mpSelectedProtocolsModel = new QStringListModel(this); + lvSelectedProtocols->setModel(mpSelectedProtocolsModel); + lvSelectedProtocols->setEditTriggers(QAbstractItemView::NoEditTriggers); + + + connect(lvAllProtocols->selectionModel(), + SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), + this, SLOT(when_lvAllProtocols_selectionChanged( + const QItemSelection&, const QItemSelection&))); + connect(lvSelectedProtocols->selectionModel(), + SIGNAL(currentChanged(const QModelIndex&, const QModelIndex&)), + this, SLOT(when_lvSelectedProtocols_currentChanged(const QModelIndex&, + const QModelIndex&))); + + LoadCurrentStream(); + mpPacketModel = new PacketModel(this); + tvPacketTree->setModel(mpPacketModel); +#ifdef QT_NO_DEBUG + mpPacketModelTester = NULL; +#else + mpPacketModelTester = new ModelTest(mpPacketModel); +#endif + tvPacketTree->header()->hide(); + vwPacketDump->setModel(mpPacketModel); + vwPacketDump->setSelectionModel(tvPacketTree->selectionModel()); + + // TODO(MED): + //! \todo Enable navigation of streams + pbPrev->setHidden(true); + pbNext->setHidden(true); + //! \todo Support Goto Stream Id + leStreamId->setHidden(true); + disconnect(rbActionGotoStream, SIGNAL(toggled(bool)), leStreamId, SLOT(setEnabled(bool))); + + switch(mPort.transmitMode()) + { + case OstProto::kSequentialTransmit: + rbModeFixed->setChecked(true); + rbModeContinuous->setDisabled(true); + break; + case OstProto::kInterleavedTransmit: + rbModeContinuous->setChecked(true); + rbModeFixed->setDisabled(true); + + nextWhat->setHidden(true); + break; + default: + Q_ASSERT(false); // Unreachable + } + + // Finally, restore the saved last geometry and selected tab for the + // various tab widgets + if (!lastGeometry.isNull()) + setGeometry(lastGeometry); + twTopLevel->setCurrentIndex(lastTopLevelTabIndex); +} + +void StreamConfigDialog::setupUiExtra() +{ + QRegExp reHex2B("[0-9,a-f,A-F]{1,4}"); + QRegExp reHex4B("[0-9,a-f,A-F]{1,8}"); + QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"); + + // ---- Setup default stuff that cannot be done in designer ---- + bgProto[ProtoL1] = new QButtonGroup(); + bgProto[ProtoL1]->addButton(rbL1None, ButtonIdNone); + bgProto[ProtoL1]->addButton(rbL1Mac, OstProto::Protocol::kMacFieldNumber); + bgProto[ProtoL1]->addButton(rbL1Other, ButtonIdOther); + + bgProto[ProtoL2] = new QButtonGroup(); +#if 0 + foreach(QRadioButton *btn, gbFrameType->findChildren()) + bgL2Proto->addButton(btn); +#else + bgProto[ProtoL2]->addButton(rbFtNone, ButtonIdNone); + bgProto[ProtoL2]->addButton(rbFtEthernet2, OstProto::Protocol::kEth2FieldNumber); + bgProto[ProtoL2]->addButton(rbFt802Dot3Raw, OstProto::Protocol::kDot3FieldNumber); + bgProto[ProtoL2]->addButton(rbFt802Dot3Llc, OstProto::Protocol::kDot2LlcFieldNumber); + bgProto[ProtoL2]->addButton(rbFtLlcSnap, OstProto::Protocol::kDot2SnapFieldNumber); + bgProto[ProtoL2]->addButton(rbFtOther, ButtonIdOther); +#endif + + bgProto[ProtoVlan] = new QButtonGroup(); + bgProto[ProtoVlan]->addButton(rbVlanNone, ButtonIdNone); + bgProto[ProtoVlan]->addButton(rbVlanSingle, OstProto::Protocol::kVlanFieldNumber); + bgProto[ProtoVlan]->addButton(rbVlanDouble, OstProto::Protocol::kVlanStackFieldNumber); + + bgProto[ProtoL3] = new QButtonGroup(); +#if 0 + foreach(QRadioButton *btn, gbL3Proto->findChildren()) + bgProto[ProtoL3]->addButton(btn); +#else + bgProto[ProtoL3]->addButton(rbL3None, ButtonIdNone); + bgProto[ProtoL3]->addButton(rbL3Arp, OstProto::Protocol::kArpFieldNumber); + bgProto[ProtoL3]->addButton(rbL3Ipv4, OstProto::Protocol::kIp4FieldNumber); + bgProto[ProtoL3]->addButton(rbL3Ipv6, OstProto::Protocol::kIp6FieldNumber); + bgProto[ProtoL3]->addButton(rbL3Ip6over4, + OstProto::Protocol::kIp6over4FieldNumber); + bgProto[ProtoL3]->addButton(rbL3Ip4over6, + OstProto::Protocol::kIp4over6FieldNumber); + bgProto[ProtoL3]->addButton(rbL3Ip4over4, + OstProto::Protocol::kIp4over4FieldNumber); + bgProto[ProtoL3]->addButton(rbL3Ip6over6, + OstProto::Protocol::kIp6over6FieldNumber); + bgProto[ProtoL3]->addButton(rbL3Other, ButtonIdOther); +#endif + + bgProto[ProtoL4] = new QButtonGroup(); +#if 0 + foreach(QRadioButton *btn, gbL4Proto->findChildren()) + bgProto[ProtoL4]->addButton(btn); +#else + bgProto[ProtoL4]->addButton(rbL4None, ButtonIdNone); + bgProto[ProtoL4]->addButton(rbL4Tcp, OstProto::Protocol::kTcpFieldNumber); + bgProto[ProtoL4]->addButton(rbL4Udp, OstProto::Protocol::kUdpFieldNumber); + bgProto[ProtoL4]->addButton(rbL4Icmp, OstProto::Protocol::kIcmpFieldNumber); + bgProto[ProtoL4]->addButton(rbL4Igmp, OstProto::Protocol::kIgmpFieldNumber); + bgProto[ProtoL4]->addButton(rbL4Mld, OstProto::Protocol::kMldFieldNumber); + bgProto[ProtoL4]->addButton(rbL4Other, ButtonIdOther); +#endif + + bgProto[ProtoL5] = new QButtonGroup(); +#if 0 + foreach(QRadioButton *btn, gbL5Proto->findChildren()) + bgProto[ProtoL5]->addButton(btn); +#else + bgProto[ProtoL5]->addButton(rbL5None, ButtonIdNone); + bgProto[ProtoL5]->addButton(rbL5Text, + OstProto::Protocol::kTextProtocolFieldNumber); + bgProto[ProtoL5]->addButton(rbL5Other, ButtonIdOther); +#endif + + bgProto[ProtoPayload] = new QButtonGroup(); +#if 0 + foreach(QRadioButton *btn, gbPayloadProto->findChildren()) + bgProto[ProtoPayload]->addButton(btn); +#else + bgProto[ProtoPayload]->addButton(rbPayloadNone, ButtonIdNone); + bgProto[ProtoPayload]->addButton(rbPayloadPattern, OstProto::Protocol::kPayloadFieldNumber); + bgProto[ProtoPayload]->addButton(rbPayloadHexDump, OstProto::Protocol::kHexDumpFieldNumber); + bgProto[ProtoPayload]->addButton(rbPayloadOther, ButtonIdOther); +#endif + /* + ** Setup Validators + */ + // Meta Data + lePktLen->setValidator(new QIntValidator(MIN_PKT_LEN, MAX_PKT_LEN, this)); + lePktLenMin->setValidator(new QIntValidator(MIN_PKT_LEN, MAX_PKT_LEN,this)); + lePktLenMax->setValidator(new QIntValidator(MIN_PKT_LEN, MAX_PKT_LEN,this)); + + lePacketsPerBurst->setValidator(new QIntValidator(1, 0x7FFFFFFF,this)); + + /* + ** Setup Connections + */ + connect(rbSendPackets, SIGNAL(toggled(bool)), + this, SLOT(update_NumPacketsAndNumBursts())); + connect(rbSendBursts, SIGNAL(toggled(bool)), + this, SLOT(update_NumPacketsAndNumBursts())); + connect(rbModeFixed, SIGNAL(toggled(bool)), + this, SLOT(update_NumPacketsAndNumBursts())); + connect(rbModeContinuous, SIGNAL(toggled(bool)), + this, SLOT(update_NumPacketsAndNumBursts())); + +} + +StreamConfigDialog::~StreamConfigDialog() +{ + delete mpPacketModelTester; + delete mpPacketModel; + + for (int i = ProtoMin; i < ProtoMax; i++) + delete bgProto[i]; + + foreach (AbstractProtocolConfigForm* w, _protocolWidgets) { + OstProtocolWidgetFactory->deleteConfigWidget(w); + } + + delete _iter; + delete mpStream; +} + +void StreamConfigDialog::loadProtocolWidgets() +{ + ProtocolListIterator *iter; + + // NOTE: Protocol Widgets are created on demand. Once created we + // store them in _protocolWidgets indexed by the protocol + // object's address (to ensure unique widgets for multiple + // objects of the same class). Subsequently we check + // _protocolWidgets before creating a new widget + iter = mpStream->createProtocolListIterator(); + while (iter->hasNext()) + { + AbstractProtocol* p = iter->next(); + AbstractProtocolConfigForm *w = _protocolWidgets.value(p); + if (!w) { + w = OstProtocolWidgetFactory->createConfigWidget( + p->protocolNumber()); + _protocolWidgets.insert(p, w); + } + w->loadWidget(p); + } + delete iter; +} + +void StreamConfigDialog::storeProtocolWidgets() +{ + ProtocolListIterator *iter; + + // NOTE: After creating a widget, we need to call loadWidget() + // to load the protocol's default values + iter = mpStream->createProtocolListIterator(); + while (iter->hasNext()) + { + AbstractProtocol* p = iter->next(); + AbstractProtocolConfigForm *w = _protocolWidgets.value(p); + if (!w) { + w = OstProtocolWidgetFactory->createConfigWidget( + p->protocolNumber()); + w->loadWidget(p); + _protocolWidgets.insert(p, w); + } + w->storeWidget(p); + } + delete iter; +} + +void StreamConfigDialog::on_cmbPktLenMode_currentIndexChanged(QString mode) +{ + if (mode == "Fixed") + { + lePktLen->setEnabled(true); + lePktLenMin->setDisabled(true); + lePktLenMax->setDisabled(true); + } + else if (mode == "Increment") + { + lePktLen->setDisabled(true); + lePktLenMin->setEnabled(true); + lePktLenMax->setEnabled(true); + } + else if (mode == "Decrement") + { + lePktLen->setDisabled(true); + lePktLenMin->setEnabled(true); + lePktLenMax->setEnabled(true); + } + else if (mode == "Random") + { + lePktLen->setDisabled(true); + lePktLenMin->setEnabled(true); + lePktLenMax->setEnabled(true); + } + else + { + qWarning("Unhandled/Unknown PktLenMode = %s", mode.toAscii().data()); + } +} + +void StreamConfigDialog::on_pbPrev_clicked() +{ +#if 0 + StoreCurrentStream(currStreamIdx); + currStreamIdx--; + LoadCurrentStream(currStreamIdx); + + pbPrev->setDisabled((currStreamIdx == 0)); + pbNext->setDisabled((currStreamIdx == 2)); +#endif +} + +void StreamConfigDialog::on_pbNext_clicked() +{ +#if 0 + StoreCurrentStream(currStreamIdx); + currStreamIdx++; + LoadCurrentStream(currStreamIdx); + + pbPrev->setDisabled((currStreamIdx == 0)); + pbNext->setDisabled((currStreamIdx == 2)); +#endif +} + +void StreamConfigDialog::on_tbSelectProtocols_currentChanged(int index) +{ + qDebug("%s, index = %d", __FUNCTION__, index); + switch (index) + { + case 0: + updateSelectProtocolsSimpleWidget(); + break; + case 1: + updateSelectProtocolsAdvancedWidget(); + break; + default: + qFatal("%s: unexpected index = %d", __FUNCTION__, index); + } +} + +void StreamConfigDialog::when_lvAllProtocols_selectionChanged( + const QItemSelection &/*selected*/, const QItemSelection &/*deselected*/) +{ + int size = lvAllProtocols->selectionModel()->selectedIndexes().size(); + + qDebug("%s: selected.indexes().size = %d\n", __FUNCTION__, size); + + tbAdd->setEnabled(size > 0); +} + +void StreamConfigDialog::when_lvSelectedProtocols_currentChanged( + const QModelIndex ¤t, const QModelIndex &/*previous*/) +{ + qDebug("%s: currentRow = %d\n", __FUNCTION__, current.row()); + + tbDelete->setEnabled(current.isValid()); + tbUp->setEnabled(current.isValid() && (current.row() != 0)); + tbDown->setEnabled(current.isValid() && + (current.row() != (current.model()->rowCount() - 1))); +} + +void StreamConfigDialog::on_tbAdd_clicked() +{ + int n = 0; + QModelIndex idx2; + QModelIndexList selection; + + selection = lvAllProtocols->selectionModel()->selectedIndexes(); + + // Validation + if (selection.size() == 0) + return; + + idx2 = lvSelectedProtocols->currentIndex(); + if (idx2.isValid()) + n = idx2.row(); + + _iter->toFront(); + while (n--) + { + if (!_iter->hasNext()) + return; + + _iter->next(); + } + + foreach(QModelIndex idx, selection) + _iter->insert(OstProtocolManager->createProtocol( + mpAvailableProtocolsModel->stringList().at(idx.row()), mpStream)); + + updateSelectProtocolsAdvancedWidget(); + lvSelectedProtocols->setCurrentIndex(idx2); +} + +void StreamConfigDialog::on_tbDelete_clicked() +{ + int n; + QModelIndex idx; + AbstractProtocol *p = NULL; + + idx = lvSelectedProtocols->currentIndex(); + + // Validation + if (!idx.isValid()) + return; + + n = idx.row() + 1; + + _iter->toFront(); + while (n--) + { + if (!_iter->hasNext()) + return; + + p = _iter->next(); + } + + Q_CHECK_PTR(p); + _iter->remove(); + // Free both protocol and associated widget + delete _protocolWidgets.take(p); + delete p; + + updateSelectProtocolsAdvancedWidget(); + lvSelectedProtocols->setCurrentIndex(idx); +} + +void StreamConfigDialog::on_tbUp_clicked() +{ + int m, n; + QModelIndex idx; + AbstractProtocol *p = NULL; + + idx = lvSelectedProtocols->currentIndex(); + + // Validation + if (!idx.isValid() || idx.row() == 0) + return; + + m = n = idx.row() + 1; + + _iter->toFront(); + while (n--) + { + if (!_iter->hasNext()) + return; + + p = _iter->next(); + } + + Q_CHECK_PTR(p); + _iter->remove(); + _iter->previous(); + _iter->insert(p); + + updateSelectProtocolsAdvancedWidget(); + lvSelectedProtocols->setCurrentIndex(idx.sibling(m-2, 0)); +} + +void StreamConfigDialog::on_tbDown_clicked() +{ + int m, n; + QModelIndex idx; + AbstractProtocol *p = NULL; + + idx = lvSelectedProtocols->currentIndex(); + + // Validation + if (!idx.isValid() || idx.row() == idx.model()->rowCount()) + return; + + m = n = idx.row() + 1; + + _iter->toFront(); + while (n--) + { + if (!_iter->hasNext()) + return; + + p = _iter->next(); + } + + Q_CHECK_PTR(p); + _iter->remove(); + _iter->next(); + _iter->insert(p); + + updateSelectProtocolsAdvancedWidget(); + lvSelectedProtocols->setCurrentIndex(idx.sibling(m,0)); +} + +void StreamConfigDialog::updateSelectProtocolsAdvancedWidget() +{ + QStringList selProtoList; + + qDebug("%s", __FUNCTION__); + + _iter->toFront(); + while(_iter->hasNext()) + { + AbstractProtocol* p = _iter->next(); + qDebug("%p -- %d", p, p->protocolNumber()); + selProtoList.append(p->shortName()); + } + mpSelectedProtocolsModel->setStringList(selProtoList); +} + +void StreamConfigDialog::on_twTopLevel_currentChanged(int index) +{ + switch (index) + { + // Protocol Data + case 1: + { + // Hide the ToolBox before modifying it - else we have a crash !!! + tbProtocolData->hide(); + + // Remove all existing protocol widgets + while (tbProtocolData->count() > 0) + { + QWidget* w = tbProtocolData->widget(0); + tbProtocolData->removeItem(0); + w->setParent(0); + } + + // Repopulate the widgets - create new ones, if required + _iter->toFront(); + while (_iter->hasNext()) + { + AbstractProtocol* p = _iter->next(); + AbstractProtocolConfigForm *w = _protocolWidgets.value(p); + if (!w) { + w = OstProtocolWidgetFactory->createConfigWidget( + p->protocolNumber()); + w->loadWidget(p); + _protocolWidgets.insert(p, w); + } + tbProtocolData->addItem(w, p->name()); + } + + if (lastProtocolDataIndex < tbProtocolData->count()) + tbProtocolData->setCurrentIndex(lastProtocolDataIndex); + + tbProtocolData->show(); + break; + } + + // Stream Control + case 2: + { + StoreCurrentStream(); + break; + } + + // Packet View + case 3: + { + StoreCurrentStream(); + mpPacketModel->setSelectedProtocols(*_iter); + break; + } + + default: + break; + } + + lastProtocolDataIndex = tbProtocolData->currentIndex(); +} + +void StreamConfigDialog::update_NumPacketsAndNumBursts() +{ + if (rbSendPackets->isChecked() && rbModeFixed->isChecked()) + leNumPackets->setEnabled(true); + else + leNumPackets->setEnabled(false); + + if (rbSendBursts->isChecked() && rbModeFixed->isChecked()) + leNumBursts->setEnabled(true); + else + leNumBursts->setEnabled(false); +} + +#if 0 +void StreamConfigDialog::on_lePattern_editingFinished() +{ + ulong num = 0; + bool isOk; + QString str; + + num = lePattern->text().remove(QChar(' ')).toULong(&isOk, 16); + qDebug("editfinished (%s | %x)\n", lePattern->text().toAscii().data(), num); + lePattern->setText(uintToHexStr(num, str, 4)); + qDebug("editfinished (%s | %x)\n", lePattern->text().toAscii().data(), num); +} +#endif + +/*! +Skip protocols upto and including the layer specified. +*/ +bool StreamConfigDialog::skipProtocols(int layer) +{ + _iter->toFront(); + + for (int i = ProtoMin; i <= layer; i++) + { + if(_iter->hasNext()) + { + int id; + QAbstractButton *btn; + + id = _iter->peekNext()->protocolNumber(); + btn = bgProto[i]->button(id); + if (btn) + _iter->next(); + } + } + + return true; +} + +/*! +Protocol choices (except "None" and "Other") for a protocol button group are disabled if checked is true, else they are enabled +*/ +void StreamConfigDialog::disableProtocols(QButtonGroup *protocolGroup, bool checked) +{ + qDebug("%s: btnGrp = %p, chk? = %d", __FUNCTION__, protocolGroup, checked); + foreach(QAbstractButton *btn, protocolGroup->buttons()) + { + int id = protocolGroup->id(btn); + + if ((id != ButtonIdNone) && (id != ButtonIdOther)) + btn->setDisabled(checked); + } +} + +void StreamConfigDialog::forceProtocolNone(bool checked) +{ + QObject *btn; + + btn = sender(); + Q_ASSERT(btn != NULL); + + qDebug("%s: chk? = %d, btn = %p, L1 = %p, L2 = %p, L3 = %p", __FUNCTION__, + checked, btn, rbL1None, rbFtNone, rbL3None); + + if (btn == rbL1None) + { + if (checked) + { + bgProto[ProtoVlan]->button(ButtonIdNone)->click(); + bgProto[ProtoL2]->button(ButtonIdNone)->click(); + bgProto[ProtoPayload]->button(ButtonIdNone)->click(); + } + + disableProtocols(bgProto[ProtoVlan], checked); + disableProtocols(bgProto[ProtoL2], checked); + disableProtocols(bgProto[ProtoPayload], checked); + } + else if (btn == rbFtNone) + { + if (checked) + bgProto[ProtoL3]->button(ButtonIdNone)->click(); + disableProtocols(bgProto[ProtoL3], checked); + } + else if (btn == rbL3None) + { + if (checked) + bgProto[ProtoL4]->button(ButtonIdNone)->click(); + disableProtocols(bgProto[ProtoL4], checked); + } + else if (btn == rbL4None) + { + if (checked) + bgProto[ProtoL5]->button(ButtonIdNone)->click(); + disableProtocols(bgProto[ProtoL5], checked); + } + else + { + Q_ASSERT(1 == 0); // Unreachable code! + } +} + +void StreamConfigDialog::updateProtocol(int newId) +{ + int level; + QButtonGroup *btnGrp; + + btnGrp = static_cast(sender()); + Q_ASSERT(btnGrp != NULL); + + level = btnGrp->property("ProtocolLevel").toInt(); + Q_ASSERT(btnGrp == bgProto[level]); + + __updateProtocol(level, newId); +} + +void StreamConfigDialog::__updateProtocol(int level, int newId) +{ + int oldId; + QButtonGroup *btnGrp; + + Q_ASSERT((level >= ProtoMin) && (level <= ProtoMax)); + btnGrp = bgProto[level]; + oldId = btnGrp->property("ProtocolId").toInt(); + + qDebug("%s: level = %d old id = %d new id = %d upd? = %d", __FUNCTION__, + level, oldId, newId, isUpdateInProgress); + + if (newId == oldId) + return; + + if (!isUpdateInProgress) + { + int ret; + AbstractProtocol *p; + + ret = skipProtocols(level-1); + Q_ASSERT(ret == true); + Q_UNUSED(ret); + + Q_ASSERT(oldId != newId); + Q_ASSERT(newId != ButtonIdOther); + + switch (oldId) + { + case ButtonIdNone: + _iter->insert(OstProtocolManager->createProtocol( + newId, mpStream)); + break; + + case ButtonIdOther: + default: + Q_ASSERT(_iter->hasNext()); + p =_iter->next(); + + if (newId) + _iter->setValue(OstProtocolManager->createProtocol( + newId, mpStream)); + else + _iter->remove(); + // Free both protocol and associated widget + delete _protocolWidgets.take(p); + delete p; + if (level == ProtoPayload) + { + while (_iter->hasNext()) + { + p = _iter->next(); + _iter->remove(); + // Free both protocol and associated widget + delete _protocolWidgets.take(p); + delete p; + } + } + break; + } + } + + btnGrp->setProperty("ProtocolId", newId); + return; +} + +void StreamConfigDialog::updateSelectProtocolsSimpleWidget() +{ + int i; + quint32 id; + QAbstractButton *btn; + + qDebug("%s", __FUNCTION__); + + isUpdateInProgress = true; + + // Reset to default state ... + for (i = ProtoMin; i < ProtoMax; i++) + bgProto[i]->button(ButtonIdNone)->click(); + + // ... now iterate and update + _iter->toFront(); + + for (i = ProtoMin; i < ProtoMax; i++) + { + if (!_iter->hasNext()) + goto _done; + + id = _iter->next()->protocolNumber(); + btn = bgProto[i]->button(id); + + if (btn) + { + if (btn->isEnabled()) + btn->click(); + else + { + btn->setChecked(true); + __updateProtocol(i, id); + } + } + else + { + switch (i) + { + case ProtoVlan: + _iter->previous(); + break; + + case ProtoPayload: + goto _other; + + default: + btn = bgProto[ProtoPayload]->button(id); + if (btn && btn->isEnabled()) + { + btn->click(); + break; + } + else + goto _other; + } + } + } + + // If more protocol(s) beyond payload ... + if (_iter->hasNext()) + { + i = ProtoPayload; + goto _other; + } + + goto _done; + +_other: + for (int j = i; j < ProtoMax; j++) + { + // VLAN doesn't have a "Other" button + if (j == ProtoVlan) + continue; + + bgProto[j]->button(ButtonIdOther)->setChecked(true); + __updateProtocol(j, ButtonIdOther); + } + +_done: + isUpdateInProgress = false; +} + +void StreamConfigDialog::LoadCurrentStream() +{ + QString str; + + qDebug("loading mpStream %p", mpStream); + + // Meta Data + { + cmbPktLenMode->setCurrentIndex(mpStream->lenMode()); + lePktLen->setText(str.setNum(mpStream->frameLen())); + lePktLenMin->setText(str.setNum(mpStream->frameLenMin())); + lePktLenMax->setText(str.setNum(mpStream->frameLenMax())); + } + + // Protocols + { + updateSelectProtocolsSimpleWidget(); + updateSelectProtocolsAdvancedWidget(); + + loadProtocolWidgets(); + } + + // Stream Control + { + switch (mpStream->sendUnit()) + { + case Stream::e_su_packets: + rbSendPackets->setChecked(true); + break; + case Stream::e_su_bursts: + rbSendBursts->setChecked(true); + break; + default: + qWarning("Unhandled sendUnit = %d\n", mpStream->sendUnit()); + } + + switch (mpStream->sendMode()) + { + case Stream::e_sm_fixed: + rbModeFixed->setChecked(true); + break; + case Stream::e_sm_continuous: + rbModeContinuous->setChecked(true); + break; + default: + qWarning("Unhandled sendMode = %d\n", mpStream->sendMode()); + } + + switch(mpStream->nextWhat()) + { + case Stream::e_nw_stop: + rbActionStop->setChecked(true); + break; + case Stream::e_nw_goto_next: + rbActionGotoNext->setChecked(true); + break; + case Stream::e_nw_goto_id: + rbActionGotoStream->setChecked(true); + break; + default: + qWarning("Unhandled nextAction = %d\n", mpStream->nextWhat()); + } + + leNumPackets->setText(QString().setNum(mpStream->numPackets())); + leNumBursts->setText(QString().setNum(mpStream->numBursts())); + lePacketsPerBurst->setText(QString().setNum(mpStream->burstSize())); + lePacketsPerSec->setText( + QString("%L1").arg(mpStream->packetRate(), 0, 'f', 4)); + leBurstsPerSec->setText( + QString("%L1").arg(mpStream->burstRate(), 0, 'f', 4)); + // TODO(MED): Change this when we support goto to specific stream + leStreamId->setText(QString("0")); + + leGapIsg->setText("0.0"); + } + qDebug("loading stream done"); +} + +void StreamConfigDialog::StoreCurrentStream() +{ + QString str; + bool isOk; + Stream *pStream = mpStream; + + qDebug("storing pStream %p", pStream); + + // Meta Data + pStream->setLenMode((Stream::FrameLengthMode) cmbPktLenMode->currentIndex()); + pStream->setFrameLen(lePktLen->text().toULong(&isOk)); + pStream->setFrameLenMin(lePktLenMin->text().toULong(&isOk)); + pStream->setFrameLenMax(lePktLenMax->text().toULong(&isOk)); + + // Protocols + { + storeProtocolWidgets(); + } + + // Stream Control + { + if (rbSendPackets->isChecked()) + pStream->setSendUnit(Stream::e_su_packets); + if (rbSendBursts->isChecked()) + pStream->setSendUnit(Stream::e_su_bursts); + + if (rbModeFixed->isChecked()) + pStream->setSendMode(Stream::e_sm_fixed); + if (rbModeContinuous->isChecked()) + pStream->setSendMode(Stream::e_sm_continuous); + + if (rbActionStop->isChecked()) + pStream->setNextWhat(Stream::e_nw_stop); + if (rbActionGotoNext->isChecked()) + pStream->setNextWhat(Stream::e_nw_goto_next); + if (rbActionGotoStream->isChecked()) + pStream->setNextWhat(Stream::e_nw_goto_id); + + pStream->setNumPackets(leNumPackets->text().toULong(&isOk)); + pStream->setNumBursts(leNumBursts->text().toULong(&isOk)); + pStream->setBurstSize(lePacketsPerBurst->text().toULong(&isOk)); + pStream->setPacketRate( + QLocale().toDouble(lePacketsPerSec->text(), &isOk)); + pStream->setBurstRate( + QLocale().toDouble(leBurstsPerSec->text(), &isOk)); + } +} + +void StreamConfigDialog::on_tbProtocolData_currentChanged(int /*index*/) +{ + // Refresh protocol widgets in case there is any dependent data between + // protocols e.g. TCP/UDP port numbers are dependent on Port/Protocol + // selection in TextProtocol +#if 0 // FIXME: temp mask to avoid crash till we fix it + storeProtocolWidgets(); + loadProtocolWidgets(); +#endif +} + +void StreamConfigDialog::on_rbPacketsPerSec_toggled(bool checked) +{ + if (checked) + on_lePacketsPerSec_textChanged(lePacketsPerSec->text()); +} + +void StreamConfigDialog::on_rbBurstsPerSec_toggled(bool checked) +{ + if (checked) + on_leBurstsPerSec_textChanged(leBurstsPerSec->text()); +} + +void StreamConfigDialog::on_lePacketsPerBurst_textChanged(const QString &/*text*/) +{ + if (rbSendBursts->isChecked()) + on_leBurstsPerSec_textChanged(leBurstsPerSec->text()); +} + +void StreamConfigDialog::on_lePacketsPerSec_textChanged(const QString &text) +{ + bool isOk; + Stream *pStream = mpStream; + uint frameLen; + + if (pStream->lenMode() == Stream::e_fl_fixed) + frameLen = pStream->frameLen(); + else + frameLen = (pStream->frameLenMin() + pStream->frameLenMax())/2; + + if (rbSendPackets->isChecked()) + { + double pktsPerSec = QLocale().toDouble(text, &isOk); + double bitsPerSec = pktsPerSec * double((frameLen+kEthFrameOverHead)*8); + + if (rbPacketsPerSec->isChecked()) + leBitsPerSec->setText(QString("%L1").arg(bitsPerSec, 0, 'f', 0)); + leGapIbg->setText(QString("0.0")); + leGapIpg->setText(QString("%L1").arg(1/double(pktsPerSec), 0, 'f', 9)); + } +} + +void StreamConfigDialog::on_leBurstsPerSec_textChanged(const QString &text) +{ + bool isOk; + Stream *pStream = mpStream; + uint burstSize = lePacketsPerBurst->text().toULong(&isOk); + uint frameLen; + + qDebug("start of %s(%s)", __FUNCTION__, text.toAscii().constData()); + if (pStream->lenMode() == Stream::e_fl_fixed) + frameLen = pStream->frameLen(); + else + frameLen = (pStream->frameLenMin() + pStream->frameLenMax())/2; + + if (rbSendBursts->isChecked()) + { + double burstsPerSec = QLocale().toDouble(text, &isOk); + double bitsPerSec = burstsPerSec * + double(burstSize * (frameLen + kEthFrameOverHead) * 8); + if (rbBurstsPerSec->isChecked()) + leBitsPerSec->setText(QString("%L1").arg(bitsPerSec, 0, 'f', 0)); + leGapIbg->setText(QString("%L1").arg(1/double(burstsPerSec), 0, 'f',9)); + leGapIpg->setText(QString("0.0")); + } + qDebug("end of %s", __FUNCTION__); +} + +void StreamConfigDialog::on_leBitsPerSec_textEdited(const QString &text) +{ + bool isOk; + Stream *pStream = mpStream; + uint burstSize = lePacketsPerBurst->text().toULong(&isOk); + uint frameLen; + + if (pStream->lenMode() == Stream::e_fl_fixed) + frameLen = pStream->frameLen(); + else + frameLen = (pStream->frameLenMin() + pStream->frameLenMax())/2; + + if (rbSendPackets->isChecked()) + { + double pktsPerSec = QLocale().toDouble(text, &isOk)/ + double((frameLen+kEthFrameOverHead)*8); + lePacketsPerSec->setText(QString("%L1").arg(pktsPerSec, 0, 'f', 4)); + } + else if (rbSendBursts->isChecked()) + { + double burstsPerSec = QLocale().toDouble(text, &isOk)/ + double(burstSize * (frameLen + kEthFrameOverHead) * 8); + leBurstsPerSec->setText(QString("%L1").arg(burstsPerSec, 0, 'f', 4)); + } +} + +void StreamConfigDialog::on_pbOk_clicked() +{ + QString log; + OstProto::Stream s; + + // Store dialog contents into stream + StoreCurrentStream(); + + if ((mPort.transmitMode() == OstProto::kInterleavedTransmit) + && (mpStream->isFrameVariable())) + { + log += "In 'Interleaved Streams' transmit mode, the count for " + "varying fields at transmit time may not be same as configured\n"; + } + + mpStream->preflightCheck(log); + + if (log.length()) + { + if (QMessageBox::warning(this, "Preflight Check", log + "\nContinue?", + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) + == QMessageBox::No) + return; + } + + // Copy the data from the "local working copy of stream" to "actual stream" + mpStream->protoDataCopyInto(s); + mPort.streamByIndex(mCurrentStreamIndex)->protoDataCopyFrom(s); + + qDebug("stream stored"); + + lastGeometry = geometry(); + lastTopLevelTabIndex = twTopLevel->currentIndex(); + lastProtocolDataIndex = tbProtocolData->currentIndex(); + + accept(); +} + diff --git a/client/streamconfigdialog.h b/client/streamconfigdialog.h new file mode 100644 index 0000000..5330414 --- /dev/null +++ b/client/streamconfigdialog.h @@ -0,0 +1,150 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _STREAM_CONFIG_DIALOG_H +#define _STREAM_CONFIG_DIALOG_H + +#include +#include "ui_streamconfigdialog.h" +#include "port.h" +#include "stream.h" +#include "packetmodel.h" +#include "modeltest.h" + +#define MAX_MAC_ITER_COUNT 256 +#define MIN_PKT_LEN 64 +#define MAX_PKT_LEN 16384 + +/* +** TODO +** \todo Improve HexStr handling +** +*/ + +class AbstractProtocolConfigForm; + +class StreamConfigDialog : public QDialog, public Ui::StreamConfigDialog +{ + Q_OBJECT +public: + StreamConfigDialog(Port &port, uint streamIndex, QWidget *parent = 0); + ~StreamConfigDialog(); + +private: + + enum ButtonId + { + ButtonIdNone = 0, + ButtonIdOther = -2 + }; + + enum ProtoButtonGroup + { + ProtoMin, + ProtoL1 = 0, + ProtoVlan = 1, + ProtoL2 = 2, + ProtoL3 = 3, + ProtoL4 = 4, + ProtoL5 = 5, + ProtoPayload = 6, + ProtoMax + }; + + QButtonGroup *bgProto[ProtoMax]; + + QStringListModel *mpAvailableProtocolsModel; + QStringListModel *mpSelectedProtocolsModel; + + Port& mPort; + uint mCurrentStreamIndex; + + Stream *mpStream; + ProtocolListIterator *_iter; + QHash _protocolWidgets; + + bool isUpdateInProgress; + + PacketModel *mpPacketModel; + ModelTest *mpPacketModelTester; + + // The following static variables are used to track the "selected" tab + // for the various tab widgets so that it can be restored when the dialog + // is opened the next time. We also track the last Dialog geometry. + static QRect lastGeometry; + static int lastTopLevelTabIndex; + static int lastProtocolDataIndex; + + void setupUiExtra(); + void LoadCurrentStream(); + void StoreCurrentStream(); + void loadProtocolWidgets(); + void storeProtocolWidgets(); + +private slots: + void on_cmbPktLenMode_currentIndexChanged(QString mode); + void update_NumPacketsAndNumBursts(); + + void on_twTopLevel_currentChanged(int index); + void on_tbSelectProtocols_currentChanged(int index); + + // "Simple" Protocol Selection related + bool skipProtocols(int layer); + + void disableProtocols(QButtonGroup *protocolGroup, bool checked); + void forceProtocolNone(bool checked); + + void updateProtocol(int newId); + void __updateProtocol(int level, int newId); + + void updateSelectProtocolsSimpleWidget(); + + // "Advanced" Protocol Selection related + void when_lvAllProtocols_selectionChanged( + const QItemSelection &selected, const QItemSelection &deselected); + void when_lvSelectedProtocols_currentChanged( + const QModelIndex ¤t, const QModelIndex &previous); + + void on_tbAdd_clicked(); + void on_tbDelete_clicked(); + void on_tbUp_clicked(); + void on_tbDown_clicked(); + + void updateSelectProtocolsAdvancedWidget(); + + // "Protocol Data" related + void on_tbProtocolData_currentChanged(int index); + + // "Stream Control" related + void on_rbPacketsPerSec_toggled(bool checked); + void on_rbBurstsPerSec_toggled(bool checked); + + void on_lePacketsPerBurst_textChanged(const QString &text); + void on_lePacketsPerSec_textChanged(const QString &text); + void on_leBurstsPerSec_textChanged(const QString &text); + void on_leBitsPerSec_textEdited(const QString &text); + + void on_pbPrev_clicked(); + void on_pbNext_clicked(); + + void on_pbOk_clicked(); +}; + +#endif + diff --git a/client/streamconfigdialog.ui b/client/streamconfigdialog.ui new file mode 100644 index 0000000..e109c79 --- /dev/null +++ b/client/streamconfigdialog.ui @@ -0,0 +1,1481 @@ + + StreamConfigDialog + + + Qt::ApplicationModal + + + + 0 + 0 + 634 + 507 + + + + + 0 + 0 + + + + Edit Stream + + + :/icons/stream_edit.png + + + QLineEdit:enabled[inputMask = "HH; "], +QLineEdit:enabled[inputMask = "HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH HH; "], +QLineEdit:enabled[inputMask = "HH HH HH HH HH HH; "] { background-color: #ccccff } + + + + true + + + + + + + + + 0 + + + + Protocol Selection + + + + + + Qt::Horizontal + + + + 241 + 20 + + + + + + + + Frame Length (including FCS) + + + + + + + Fixed + + + + + Increment + + + + + Decrement + + + + + Random + + + + + + + + Min + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Max + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + 0 + + + + + 0 + 0 + 592 + 269 + + + + Simple + + + + + + L1 + + + + + + None + + + true + + + + + + + Mac + + + false + + + + + + + false + + + Other + + + + + + + + + + true + + + L2 + + + + + + None + + + true + + + + + + + Ethernet II + + + false + + + + + + + 802.3 Raw + + + + + + + 802.3 LLC + + + false + + + + + + + 802.3 LLC SNAP + + + + + + + false + + + Other + + + + + + + + + + true + + + L3 + + + + + + None + + + true + + + + + + + false + + + ARP + + + + + + + false + + + IPv4 + + + false + + + + + + + false + + + IPv6 + + + + + + + false + + + IP 6over4 + + + false + + + + + + + false + + + IP 4over6 + + + false + + + + + + + false + + + IP 4over4 + + + false + + + + + + + false + + + IP 6over6 + + + false + + + + + + + false + + + Other + + + + + + + + + + true + + + L5 + + + + + + None + + + true + + + + + + + false + + + Text + + + + + + + false + + + Other + + + + + + + + + + true + + + VLAN + + + false + + + false + + + + + + Untagged + + + true + + + + + + + Tagged + + + + + + + Stacked + + + + + + + + + + true + + + L4 + + + + + + None + + + true + + + + + + + false + + + ICMP + + + + + + + false + + + IGMP + + + + + + + false + + + TCP + + + + + + + false + + + UDP + + + + + + + false + + + Other + + + + + + + false + + + MLD + + + + + + + + + + true + + + Payload + + + + + + None + + + true + + + + + + + Pattern + + + false + + + + + + + Hex Dump + + + + + + + false + + + Other + + + + + + + + + + + + 0 + 0 + 250 + 135 + + + + Advanced + + + + + + + + Available Protocols + + + + + + + true + + + QAbstractItemView::ExtendedSelection + + + QAbstractItemView::SelectRows + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + false + + + > + + + :/icons/arrow_right.png + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Selected Protocols + + + + + + + + + false + + + ^ + + + :/icons/arrow_up.png + + + + + + + false + + + v + + + :/icons/arrow_down.png + + + + + + + false + + + - + + + :/icons/delete.png + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + QAbstractItemView::SelectRows + + + + + + + + + + + + + + Protocol Data + + + + + + -1 + + + + + + + + Stream Control + + + + + + Send + + + + + + Packets + + + true + + + + + + + Bursts + + + + + + + + + + Numbers + + + + + + Number of Packets + + + leNumPackets + + + + + + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Number of Bursts + + + leNumBursts + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Packets per Burst + + + lePacketsPerBurst + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Rate + + + false + + + false + + + + + + Packets/Sec + + + true + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + false + + + Bursts/Sec + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Bits/Sec + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + After this stream + + + + + + Stop + + + + + + + Goto Next Stream + + + true + + + + + + + Goto First + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Horizontal + + + + 20 + 41 + + + + + + + + Mode + + + + + + Fixed + + + true + + + + + + + Continuous + + + + + + + + + + true + + + Gaps (in seconds) + + + false + + + false + + + + + + + + + :/icons/gaps.png + + + + + + + ISG + + + leGapIsg + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + IBG + + + leGapIbg + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + IPG + + + leGapIpg + + + + + + + false + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 153 + 21 + + + + + + + + + Packet View + + + + + + Qt::Vertical + + + + QAbstractItemView::SelectItems + + + QAbstractItemView::ScrollPerPixel + + + true + + + + + + + + + + + + + + + Prev + + + + + + + Next + + + + + + + Qt::Horizontal + + + + 191 + 20 + + + + + + + + OK + + + true + + + + + + + Cancel + + + + + + + + + + DumpView + QWidget +
dumpview.h
+ 1 +
+
+ + twTopLevel + cmbPktLenMode + lePktLen + lePktLenMin + lePktLenMax + rbL1None + rbL1Mac + rbL1Other + rbVlanNone + rbVlanSingle + rbVlanDouble + rbFtNone + rbFtEthernet2 + rbFt802Dot3Raw + rbFt802Dot3Llc + rbFtLlcSnap + rbFtOther + rbL3None + rbL3Arp + rbL3Ipv4 + rbL3Ipv6 + rbL3Ip6over4 + rbL3Ip4over6 + rbL3Ip4over4 + rbL3Ip6over6 + rbL3Other + rbL4None + rbL4Icmp + rbL4Igmp + rbL4Mld + rbL4Tcp + rbL4Udp + rbL4Other + rbL5None + rbL5Text + rbL5Other + rbPayloadNone + rbPayloadPattern + rbPayloadHexDump + rbPayloadOther + lvAllProtocols + tbAdd + tbUp + tbDown + tbDelete + lvSelectedProtocols + rbSendPackets + rbSendBursts + rbModeFixed + rbModeContinuous + leNumPackets + leNumBursts + lePacketsPerBurst + lePacketsPerSec + leBurstsPerSec + rbBitsPerSec + leBitsPerSec + rbActionStop + rbActionGotoNext + rbActionGotoStream + leStreamId + leGapIsg + leGapIbg + leGapIpg + tvPacketTree + pbPrev + pbNext + pbOk + pbCancel + + + + + + + pbCancel + clicked() + StreamConfigDialog + reject() + + + 623 + 496 + + + 533 + 466 + + + + + rbActionGotoStream + toggled(bool) + leStreamId + setEnabled(bool) + + + 463 + 143 + + + 463 + 177 + + + + + rbSendPackets + toggled(bool) + rbPacketsPerSec + setEnabled(bool) + + + 30 + 68 + + + 299 + 82 + + + + + rbSendBursts + toggled(bool) + rbBurstsPerSec + setEnabled(bool) + + + 30 + 95 + + + 299 + 132 + + + + + rbSendBursts + toggled(bool) + lePacketsPerBurst + setEnabled(bool) + + + 30 + 95 + + + 134 + 189 + + + + + rbPacketsPerSec + toggled(bool) + lePacketsPerSec + setEnabled(bool) + + + 299 + 82 + + + 299 + 108 + + + + + rbBurstsPerSec + toggled(bool) + leBurstsPerSec + setEnabled(bool) + + + 299 + 132 + + + 299 + 158 + + + + + rbBitsPerSec + toggled(bool) + leBitsPerSec + setEnabled(bool) + + + 299 + 182 + + + 299 + 208 + + + + + rbSendPackets + toggled(bool) + rbPacketsPerSec + setChecked(bool) + + + 95 + 70 + + + 299 + 82 + + + + + rbSendBursts + toggled(bool) + rbBurstsPerSec + setChecked(bool) + + + 96 + 98 + + + 299 + 132 + + + + + rbModeContinuous + toggled(bool) + leNumPackets + setDisabled(bool) + + + 73 + 196 + + + 164 + 108 + + + + + rbModeContinuous + toggled(bool) + leNumBursts + setDisabled(bool) + + + 96 + 199 + + + 226 + 155 + + + + +
diff --git a/client/streamlistdelegate.cpp b/client/streamlistdelegate.cpp new file mode 100644 index 0000000..f15bc18 --- /dev/null +++ b/client/streamlistdelegate.cpp @@ -0,0 +1,194 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include +#include +#include +#include + +#include "streammodel.h" +#include "streamlistdelegate.h" + +StreamListDelegate::StreamListDelegate(QObject *parent) +: QItemDelegate(parent) +{ +} + + +QWidget *StreamListDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QWidget *editor = NULL; + + switch ((StreamModel::StreamFields) index.column()) + { + case StreamModel::StreamStatus: + { + editor = new QCheckBox(parent); + goto _handled; + } + case StreamModel::StreamNextWhat: + { + editor = new QComboBox(parent); + static_cast(editor)->insertItems(0, + StreamModel::nextWhatOptionList()); + goto _handled; + } + + case StreamModel::StreamIcon: + case StreamModel::StreamName: + default: + break; + } + + editor = QItemDelegate::createEditor(parent, option, index); + +_handled: + return editor; + +} + + +void StreamListDelegate::setEditorData(QWidget *editor, + const QModelIndex &index) const +{ + switch ((StreamModel::StreamFields) index.column()) + { + case StreamModel::StreamStatus: + { + QCheckBox *cb = static_cast(editor); + cb->setChecked( + index.model()->data(index, Qt::EditRole).toBool()); + goto _handled; + } + case StreamModel::StreamNextWhat: + { + QComboBox *cb = static_cast(editor); + cb->setCurrentIndex( + index.model()->data(index, Qt::EditRole).toInt()); + goto _handled; + } + + case StreamModel::StreamIcon: + case StreamModel::StreamName: + default: + break; + } + + QItemDelegate::setEditorData(editor, index); + +_handled: + return; +} + + +void StreamListDelegate::setModelData(QWidget *editor, + QAbstractItemModel *model, const QModelIndex &index) const +{ + switch ((StreamModel::StreamFields) index.column()) + { + case StreamModel::StreamStatus: + { + QCheckBox *cb = static_cast(editor); + model->setData(index, cb->isChecked(), Qt::EditRole); + goto _handled; + } + + case StreamModel::StreamNextWhat: + { + QComboBox *cb = static_cast(editor); + model->setData(index, cb->currentIndex(), Qt::EditRole); + goto _handled; + } + + case StreamModel::StreamIcon: + case StreamModel::StreamName: + default: + break; + } + + QItemDelegate::setModelData(editor, model, index); + +_handled: + return; +} + + +void StreamListDelegate::updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + switch ((StreamModel::StreamFields) index.column()) + { + case StreamModel::StreamStatus: + { + /* + * extra 'coz QItemDelegate does it - otherwise the editor + * placement is incorrect + */ + int extra = 2 * (qApp->style()->pixelMetric( + QStyle::PM_FocusFrameHMargin, 0) + 1); + + editor->setGeometry(option.rect.translated(extra, 0)); + goto _handled; + } + case StreamModel::StreamIcon: + case StreamModel::StreamName: + case StreamModel::StreamNextWhat: + default: + break; + } + + QItemDelegate::updateEditorGeometry(editor, option, index); + +_handled: + return; +} + + +bool StreamListDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + /* + * Special Handling so that user can use the "Stream status" checkbox + * without double clicking first. Copied from QItemDelegate::editorEvent() + * and modified suitably + */ + if ((StreamModel::StreamFields)index.column() == + StreamModel::StreamStatus) + { + // make sure that we have the right event type + if ((event->type() == QEvent::MouseButtonRelease) + || (event->type() == QEvent::MouseButtonDblClick)) + { + QRect checkRect = check(option, option.rect, Qt::Checked); + QRect emptyRect; + doLayout(option, &checkRect, &emptyRect, &emptyRect, false); + if (!checkRect.contains(static_cast(event)->pos())) + return false; + + Qt::CheckState state = (static_cast(index.data( + Qt::CheckStateRole).toInt()) == Qt::Checked ? Qt::Unchecked : Qt::Checked); + return model->setData(index, state, Qt::CheckStateRole); + } + } + + return QItemDelegate::editorEvent(event, model, option, index); +} + diff --git a/client/streamlistdelegate.h b/client/streamlistdelegate.h new file mode 100644 index 0000000..a98a34e --- /dev/null +++ b/client/streamlistdelegate.h @@ -0,0 +1,48 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef STREAM_LIST_DELEGATE_H +#define STREAM_LIST_DELEGATE_H + +#include +#include + +class StreamListDelegate : public QItemDelegate +{ + Q_OBJECT + +public: + StreamListDelegate(QObject *parent = 0); + + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; + + void setEditorData(QWidget *editor, const QModelIndex &index) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, + const QModelIndex &index) const; + + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const; + + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); +}; + +#endif + diff --git a/client/streammodel.cpp b/client/streammodel.cpp new file mode 100644 index 0000000..c66f02c --- /dev/null +++ b/client/streammodel.cpp @@ -0,0 +1,288 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "stream.h" +#include "streammodel.h" +#include "portgrouplist.h" +#include "qicon.h" + +StreamModel::StreamModel(PortGroupList *p, QObject *parent) + : QAbstractTableModel(parent) +{ + pgl = p; + mCurrentPort = NULL; +} + +int StreamModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + + if (mCurrentPort) + return mCurrentPort->numStreams(); + else + return 0; +} + +int StreamModel::columnCount(const QModelIndex &/*parent*/) const +{ + int count = StreamMaxFields; + if (mCurrentPort && + (mCurrentPort->transmitMode() == OstProto::kInterleavedTransmit)) + count--; + + return count; +} + +Qt::ItemFlags StreamModel::flags(const QModelIndex &index) const +{ + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + + + switch (index.column()) + { + case StreamIcon: + break; + case StreamName: + flags |= Qt::ItemIsEditable; + break; + case StreamStatus: + flags |= Qt::ItemIsUserCheckable; + break; + case StreamNextWhat: + flags |= Qt::ItemIsEditable; + break; + default: + //qFatal("Missed case in switch!"); + break; + } + + return flags; +} + +QVariant StreamModel::data(const QModelIndex &index, int role) const +{ + // Check for a valid index + if (!index.isValid()) + return QVariant(); + + // Check for row/column limits + if (index.row() >= mCurrentPort->numStreams()) + return QVariant(); + + if (index.column() >= StreamMaxFields) + return QVariant(); + + if (mCurrentPort == NULL) + return QVariant(); + + // Return data based on field and role + switch(index.column()) + { + case StreamIcon: + { + if (role == Qt::DecorationRole) + return QIcon(":/icons/stream_edit.png"); + else + return QVariant(); + break; + } + case StreamName: + { + if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) + return mCurrentPort->streamByIndex(index.row())->name(); + else + return QVariant(); + break; + } + case StreamStatus: + { + if ((role == Qt::CheckStateRole)) + { + if (mCurrentPort->streamByIndex(index.row())->isEnabled()) + return Qt::Checked; + else + return Qt::Unchecked; + } + else + return QVariant(); + break; + } + case StreamNextWhat: + { + int val = mCurrentPort->streamByIndex(index.row())->nextWhat(); + + if (role == Qt::DisplayRole) + return nextWhatOptionList().at(val); + else if (role == Qt::EditRole) + return val; + else + return QVariant(); + + break; + } + default: + qFatal("-------------UNHANDLED STREAM FIELD----------------"); + } + + return QVariant(); +} + +bool StreamModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (mCurrentPort == NULL) + return false; + + if (index.isValid()) + { + switch (index.column()) + { + // Edit Supported Fields + case StreamName: + mCurrentPort->streamByIndex(index.row())->setName(value.toString()); + emit(dataChanged(index, index)); + return true; + + case StreamStatus: + mCurrentPort->streamByIndex(index.row())->setEnabled(value.toBool()); + emit(dataChanged(index, index)); + return true; + + case StreamNextWhat: + if (role == Qt::EditRole) + { + mCurrentPort->streamByIndex(index.row())->setNextWhat( + (Stream::NextWhat)value.toInt()); + emit(dataChanged(index, index)); + return true; + } + else + return false; + + // Edit Not Supported Fields + case StreamIcon: + return false; + + // Unhandled Stream Field + default: + qDebug("-------------UNHANDLED STREAM FIELD----------------"); + break; + } + } + + return false; +} + +QVariant StreamModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole) + return QVariant(); + + if (orientation == Qt::Horizontal) + { + switch(section) + { + case StreamIcon: + return QString(""); + break; + case StreamName: + return QString("Name"); + break; + case StreamStatus: + return QString(""); + break; + case StreamNextWhat: + return QString("Goto"); + break; + default: + qDebug("-------------UNHANDLED STREAM FIELD----------------"); + break; + } + } + else + return QString("%1").arg(section+1); + + return QVariant(); +} + +bool StreamModel::insertRows(int row, int count, const QModelIndex &/*parent*/) +{ + qDebug("insertRows() row = %d", row); + qDebug("insertRows() count = %d", count); + beginInsertRows(QModelIndex(), row, row+count-1); + for (int i = 0; i < count; i++) + mCurrentPort->newStreamAt(row); + endInsertRows(); + + return true; +} + +bool StreamModel::removeRows(int row, int count, const QModelIndex &/*parent*/) +{ + qDebug("removeRows() row = %d", row); + qDebug("removeRows() count = %d", count); + beginRemoveRows(QModelIndex(), row, row+count-1); + for (int i = 0; i < count; i++) + { + mCurrentPort->deleteStreamAt(row); + } + endRemoveRows(); + + return true; +} + +// --------------------- SLOTS ------------------------ + +void StreamModel::setCurrentPortIndex(const QModelIndex ¤t) +{ + if (!current.isValid() || !pgl->isPort(current)) + { + qDebug("current is either invalid or not a port"); + mCurrentPort = NULL; + } + else + { + qDebug("change to valid port"); + // Disconnect any existing connection to avoid duplication + // Qt 4.6 has Qt::UniqueConnection, but we want to remain compatible + // with earlier Qt versions + if (mCurrentPort) + { + disconnect(mCurrentPort, SIGNAL(streamListChanged(int, int)), + this, SLOT(when_mCurrentPort_streamListChanged(int, int))); + } + quint16 pg = current.internalId() >> 16; + mCurrentPort = pgl->mPortGroups[pgl->indexOfPortGroup(pg)]->mPorts[current.row()]; + connect(mCurrentPort, SIGNAL(streamListChanged(int, int)), + this, SLOT(when_mCurrentPort_streamListChanged(int, int))); + } + reset(); +} + +void StreamModel::when_mCurrentPort_streamListChanged(int portGroupId, + int portId) +{ + qDebug("In %s", __FUNCTION__); + if (mCurrentPort) + { + if ((quint32(portGroupId) == mCurrentPort->portGroupId()) + && (quint32(portId) == mCurrentPort->id())) + reset(); + } +} diff --git a/client/streammodel.h b/client/streammodel.h new file mode 100644 index 0000000..d559618 --- /dev/null +++ b/client/streammodel.h @@ -0,0 +1,78 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _STREAM_MODEL_H +#define _STREAM_MODEL_H + +#include +#include +#include "port.h" + +class PortGroupList; + +class StreamModel : public QAbstractTableModel +{ + Q_OBJECT + + Port *mCurrentPort; + PortGroupList *pgl; + + public: + StreamModel(PortGroupList *p, QObject *parent = 0); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, + int role = Qt::EditRole); + QVariant headerData(int section, Qt::Orientation orientation, + int role = Qt::DisplayRole) const; + bool insertRows (int row, int count, + const QModelIndex & parent = QModelIndex()); + bool removeRows (int row, int count, + const QModelIndex & parent = QModelIndex()); + +#if 0 // CleanedUp! + // FIXME(HIGH): This *is* like a kludge + QList* currentPortStreamList() + { return &mCurrentPort->mStreams; } +#endif + + public: + enum StreamFields { + StreamIcon = 0, + StreamStatus, + StreamName, + StreamNextWhat, + + StreamMaxFields + }; + + static QStringList nextWhatOptionList() + { return QStringList() << "Stop" << "Next" << "Goto first"; } + + public slots: + void setCurrentPortIndex(const QModelIndex ¤t); + + private slots: + void when_mCurrentPort_streamListChanged(int portGroupId, int portId); +}; + +#endif diff --git a/client/updater.cpp b/client/updater.cpp new file mode 100644 index 0000000..669cfb2 --- /dev/null +++ b/client/updater.cpp @@ -0,0 +1,127 @@ +/* +Copyright (C) 2015 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "updater.h" + +#include +#include +#include + +extern const char* version; + +Updater::Updater() +{ + http_ = NULL; + file_ = NULL; + +#if 1 + // Tests! + Q_ASSERT(isVersionNewer("1.1", "1") == true); + Q_ASSERT(isVersionNewer("10.1", "2") == true); + Q_ASSERT(isVersionNewer("0.10", "0.2") == true); + Q_ASSERT(isVersionNewer("1.10.1", "1.2.3") == true); +#endif +} + +Updater::~Updater() +{ + delete http_; + delete file_; +} + +void Updater::checkForNewVersion() +{ + http_ = new QHttp("wiki.ostinato.googlecode.com"); + file_ = new QTemporaryFile(); + + connect(http_, SIGNAL(responseHeaderReceived(QHttpResponseHeader)), + this, SLOT(responseReceived(QHttpResponseHeader))); + connect(http_, SIGNAL(requestFinished(int, bool)), + this, SLOT(parseXml(int, bool))); + connect(http_, SIGNAL(stateChanged(int)), + this, SLOT(stateUpdate(int))); + + file_->open(); + qDebug("Updater: PAD XML file - %s", qPrintable(file_->fileName())); + + http_->get("/hg/html/pad.xml", file_); + qDebug("Updater: %s", qPrintable(http_->currentRequest().toString() + .replace("\r\n", "\nUpdater: "))); +} + +void Updater::stateUpdate(int state) +{ + qDebug("Updater: state %d", state); +} + +void Updater::responseReceived(QHttpResponseHeader response) +{ + qDebug("Updater: HTTP/%d.%d %d %s", + response.majorVersion(), response.minorVersion(), + response.statusCode(), qPrintable(response.reasonPhrase())); +} + +void Updater::parseXml(int id, bool error) +{ + QXmlStreamReader xml; + QString newVersion; + + if (error) { + qDebug("Updater: %s", qPrintable(http_->errorString())); + goto _exit; + } + + // Close and reopen the file so that we read from the top + file_->close(); + file_->open(); + xml.setDevice(file_); + + while (!xml.atEnd()) { + xml.readNext(); + if (xml.isStartElement() && (xml.name() == "Program_Version")) + newVersion = xml.readElementText(); + } + + qDebug("Updater: latest version = %s", qPrintable(newVersion)); + if (!newVersion.isEmpty() && isVersionNewer(newVersion, QString(version))) + emit newVersionAvailable(newVersion); + +_exit: + // Job done, time to self-destruct + deleteLater(); +} + +bool Updater::isVersionNewer(QString newVersion, QString curVersion) +{ + QStringList curVer = QString(curVersion).split('.'); + QStringList newVer = QString(newVersion).split('.'); + + for (int i = 0; i < qMin(curVer.size(), newVer.size()); i++) { + bool isOk; + if (newVer.at(i).toUInt(&isOk) > curVer.at(i).toUInt(&isOk)) + return true; + } + + if (newVer.size() > curVer.size()) + return true; + + return false; +} + + diff --git a/client/updater.h b/client/updater.h new file mode 100644 index 0000000..2d61e3d --- /dev/null +++ b/client/updater.h @@ -0,0 +1,52 @@ +/* +Copyright (C) 2015 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _UPDATER_H +#define _UPDATER_H + +#include +#include + +class QHttp; +class QTemporaryFile; + +class Updater : public QObject +{ + Q_OBJECT +public: + Updater(); + virtual ~Updater(); + void checkForNewVersion(); + static bool isVersionNewer(QString newVersion, QString curVersion); + +signals: + void newVersionAvailable(QString); + +private slots: + void stateUpdate(int state); + void responseReceived(QHttpResponseHeader response); + void parseXml(int id, bool error); + +private: + QHttp *http_; + QTemporaryFile *file_; +}; + +#endif + diff --git a/common/abstractfileformat.cpp b/common/abstractfileformat.cpp new file mode 100644 index 0000000..234795a --- /dev/null +++ b/common/abstractfileformat.cpp @@ -0,0 +1,132 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "abstractfileformat.h" + +#include "fileformat.h" +#include "pcapfileformat.h" +#include "pdmlfileformat.h" +#include "pythonfileformat.h" + +#include + +AbstractFileFormat::AbstractFileFormat() +{ + stop_ = false; +} + +AbstractFileFormat::~AbstractFileFormat() +{ +} + +QDialog* AbstractFileFormat::openOptionsDialog() +{ + return NULL; +} + +QDialog* AbstractFileFormat::saveOptionsDialog() +{ + return NULL; +} + +QStringList AbstractFileFormat::supportedFileTypes() +{ + return QStringList() + << "Ostinato (*)" + << "PCAP (*)" + << "PDML (*.pdml)" + << "PythonScript (*.py)"; +} + +void AbstractFileFormat::openStreamsOffline(const QString fileName, + OstProto::StreamConfigList &streams, QString &error) +{ + fileName_ = fileName; + openStreams_ = &streams; + error_ = &error; + op_ = kOpen; + stop_ = false; + + start(); +} + +void AbstractFileFormat::saveStreamsOffline( + const OstProto::StreamConfigList streams, + const QString fileName, QString &error) +{ + saveStreams_ = streams; + fileName_ = fileName; + error_ = &error; + op_ = kSave; + stop_ = false; + + start(); +} + +bool AbstractFileFormat::result() +{ + return result_; +} + +AbstractFileFormat* AbstractFileFormat::fileFormatFromFile( + const QString fileName) +{ + if (fileFormat.isMyFileFormat(fileName)) + return &fileFormat; + + if (pdmlFileFormat.isMyFileFormat(fileName)) + return &pdmlFileFormat; + + if (pcapFileFormat.isMyFileFormat(fileName)) + return &pcapFileFormat; + + return NULL; +} + +AbstractFileFormat* AbstractFileFormat::fileFormatFromType( + const QString fileType) +{ + + if (fileFormat.isMyFileType(fileType)) + return &fileFormat; + + if (pdmlFileFormat.isMyFileType(fileType)) + return &pdmlFileFormat; + + if (pcapFileFormat.isMyFileType(fileType)) + return &pcapFileFormat; + + if (pythonFileFormat.isMyFileType(fileType)) + return &pythonFileFormat; + + return NULL; +} + +void AbstractFileFormat::cancel() +{ + stop_ = true; +} + +void AbstractFileFormat::run() +{ + if (op_ == kOpen) + result_ = openStreams(fileName_, *openStreams_, *error_); + else if (op_ == kSave) + result_ = saveStreams(saveStreams_, fileName_, *error_); +} diff --git a/common/abstractfileformat.h b/common/abstractfileformat.h new file mode 100644 index 0000000..1f8447d --- /dev/null +++ b/common/abstractfileformat.h @@ -0,0 +1,91 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ABSTRACT_FILE_FORMAT_H +#define _ABSTRACT_FILE_FORMAT_H + +#include "protocol.pb.h" + +#include +#include + +class QDialog; + +class AbstractFileFormat : public QThread +{ + Q_OBJECT +public: + AbstractFileFormat(); + virtual ~AbstractFileFormat(); + + virtual bool openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error) = 0; + virtual bool saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error) = 0; + + virtual QDialog* openOptionsDialog(); + virtual QDialog* saveOptionsDialog(); + + void openStreamsOffline(const QString fileName, + OstProto::StreamConfigList &streams, QString &error); + void saveStreamsOffline(const OstProto::StreamConfigList streams, + const QString fileName, QString &error); + + bool result(); + + static QStringList supportedFileTypes(); + + static AbstractFileFormat* fileFormatFromFile(const QString fileName); + static AbstractFileFormat* fileFormatFromType(const QString fileType); + +#if 0 + bool isMyFileFormat(const QString fileName) = 0; + bool isMyFileType(const QString fileType) = 0; +#endif + +signals: + void status(QString text); + void target(int value); + void progress(int value); + +public slots: + void cancel(); + +protected: + void run(); + + bool stop_; + +private: + enum kOp + { + kOpen, + kSave + }; + QString fileName_; + OstProto::StreamConfigList *openStreams_; + OstProto::StreamConfigList saveStreams_; + QString *error_; + kOp op_; + bool result_; + +}; + +#endif + diff --git a/common/abstractprotocol.cpp b/common/abstractprotocol.cpp new file mode 100644 index 0000000..230fd81 --- /dev/null +++ b/common/abstractprotocol.cpp @@ -0,0 +1,898 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "abstractprotocol.h" + +#include "protocollistiterator.h" +#include "streambase.h" + +#include + +/*! + \class AbstractProtocol + + AbstractProtocol is the base abstract class which provides the interface + for all protocols. + + All protocols supported by Ostinato are derived from AbstractProtocol. Apart + from defining the interface for a protocol, it also provides sensible default + implementations for methods so that the subclasses need not re-implement. It + also provides convenience functions for subclasses to use such as methods to + retrieve payload size, checksum etc. + + A subclass typically needs to reimplement the following methods - + - name() + - shortName() + - createInstance() + - protocolNumber() + - protoDataCopyInto() [pure virtual] + - protoDataCopyFrom() [pure virtual] + - fieldCount() + - fieldFlags() + - fieldData() + - setFieldData() + + Depending on certain conditions, subclasses may need to reimplement the + following additional methods - + - protocolIdType() + - protocolId() + - protocolFrameSize() + - isProtocolFrameValueVariable() + - isProtocolFrameSizeVariable() + - protocolFrameVariableCount() + + See the description of the methods for more information. + + Most of the above methods just need some standard boilerplate code - + the SampleProtocol implementation includes the boilerplate +*/ + +/*! + Constructs an abstract protocol for the given stream and parent + + parent is typically NULL except for protocols which are part of a + ComboProtocol +*/ +AbstractProtocol::AbstractProtocol(StreamBase *stream, AbstractProtocol *parent) +{ + //qDebug("%s: &prev = %p &next = %p", __FUNCTION__, &prev, &next); + mpStream = stream; + this->parent = parent; + prev = next = NULL; + _metaFieldCount = -1; + _frameFieldCount = -1; + protoSize = -1; + _hasPayload = true; +} + +/*! + Destroys the abstract protocol +*/ +AbstractProtocol::~AbstractProtocol() +{ +} + +/*! + Allocates and returns a new instance of the class. + + Caller is responsible for freeing up after use. Subclasses MUST implement + this function +*/ +AbstractProtocol* AbstractProtocol::createInstance(StreamBase* /* stream */, + AbstractProtocol* /* parent */) +{ + return NULL; +} + +/*! + Returns the protocol's field number as defined in message 'Protocol', enum 'k' + (file: protocol.proto) + + Subclasses MUST implement this function + + \todo convert this to a protected data member instead of a virtual function +*/ +quint32 AbstractProtocol::protocolNumber() const +{ + qFatal("Something wrong!!!"); + return 0xFFFFFFFF; +} + +/*! + \fn virtual void AbstractProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const = 0 + + Copy the protocol's protobuf as an extension into the passed in protocol + + In the base class this is a pure virtual function. Subclasses MUST implement + this function. See the SampleProtocol for an example +*/ + + +/*! + \fn virtual void AbstractProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) = 0 + + Copy and update the protocol's protobuf member data variable from the + passed in protocol + + In the base class this is a pure virtual function. Subclasses MUST implement + this function. See the SampleProtocol for an example +*/ + + +/*! + Returns the full name of the protocol + + The default implementation returns a null string +*/ +QString AbstractProtocol::name() const +{ + return QString(); +} + +/*! + Returns the short name or abbreviation of the protocol + + The default implementation forms and returns an abbreviation composed + of all the upper case chars in name() \n + The default implementation caches the abbreviation on its first invocation + and subsequently returns the cached abbreviation +*/ +QString AbstractProtocol::shortName() const +{ + if (protoAbbr.isNull()) + { + QString abbr; + + for (int i = 0; i < name().size(); i++) + if (name().at(i).isUpper()) abbr.append(name().at(i)); + + if (abbr.size()) + protoAbbr = abbr; + else + protoAbbr = QString(""); + } + + return protoAbbr; +} + +/*! + Returns the number of fields in the protocol (both Frame fields and + Meta fields) + + The default implementation returns zero. Subclasses MUST implement this + function. +*/ +int AbstractProtocol::fieldCount() const +{ + return 0; +} + +/*! + Returns the number of meta fields + + The default implementation counts and returns the number of fields for which + the MetaField flag is set\n + The default implementation caches the count on its first invocation + and subsequently returns the cached count +*/ +int AbstractProtocol::metaFieldCount() const +{ + if (_metaFieldCount < 0) + { + int c = 0; + for (int i = 0; i < fieldCount() ; i++) + if (fieldFlags(i).testFlag(MetaField)) + c++; + _metaFieldCount = c; + } + + return _metaFieldCount; +} + +/*! + Returns the number of frame fields + + The default implementation counts and returns the number of fields for which + the FrameField flag is set\n + The default implementation caches the count on its first invocation + and subsequently returns the cached count + + Subclasses which export different sets of fields based on a opcode/type + (e.g. icmp) should re-implement this function +*/ +int AbstractProtocol::frameFieldCount() const +{ + if (_frameFieldCount < 0) + { + int c = 0; + for (int i = 0; i < fieldCount() ; i++) + if (fieldFlags(i).testFlag(FrameField)) + c++; + _frameFieldCount = c; + } + + return _frameFieldCount; +} + +/*! + Returns the field flags for the passed in field index + + The default implementation assumes all fields to be frame fields and returns + 'FrameField'. Subclasses must reimplement this method if they have any + meta fields or checksum fields. See the SampleProtocol for an example. +*/ +AbstractProtocol::FieldFlags AbstractProtocol::fieldFlags(int /*index*/) const +{ + return FrameField; +} + +/*! + Returns the requested field attribute data + + Protocols which have meta fields that vary a frame field across + streams may use the streamIndex to return the appropriate field value \n + Some field attributes e.g. FieldName may be invariant across streams\n + The FieldTextValue attribute may include additional information about + the field's value e.g. a checksum field may include "(correct)" or + "(incorrect)" alongwith the actual checksum value. \n + + The default implementation returns a empty string for FieldName and + FieldTextValue; empty byte array of size 0 for FieldFrameValue; 0 for + FieldValue; subclasses are expected to return meaning values for all + these attributes. The only exception is the 'FieldBitSize' attribute - + the default implementation takes the (byte) size of FieldFrameValue, + multiplies it with 8 and returns the result - this can be used by + subclasses for fields which are an integral multiple of bytes; for + fields whose size are a non-integral multiple of bytes or smaller than + a byte, subclasses should return the correct value. Also for fields + which represent checksums, subclasses should return a value for + FieldBitSize - even if it is an integral multiple of bytes. + + \note If a subclass uses any of the below functions to derive + FieldFrameValue, the subclass should handle and return a value for + FieldBitSize to prevent endless recursion - + - protocolFrameCksum() + - protocolFramePayloadSize() +*/ +QVariant AbstractProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (attrib) + { + case FieldName: + return QString(); + case FieldBitSize: + Q_ASSERT_X(!fieldFlags(index).testFlag(CksumField), + "AbstractProtocol::fieldData()", + "FieldBitSize for checksum fields need to be handled by the subclass"); + return fieldData(index, FieldFrameValue, streamIndex). + toByteArray().size() * 8; + case FieldValue: + return 0; + case FieldFrameValue: + return QByteArray(); + case FieldTextValue: + return QString(); + + default: + qFatal("%s:%d: unhandled case %d\n", __FUNCTION__, __LINE__, + attrib); + } + + return QVariant(); +} + +/*! + Sets the value of a field corresponding to index + + This method is called by the GUI code to store a user specified value into + the protocol's protoBuf. Currently this method is called with + FieldAttrib = FieldValue only. + + Returns true if field is successfully set, false otherwise. + The default implementation always returns false. Subclasses should + reimplement this method. See SampleProtocol for an example. + +*/ +bool AbstractProtocol::setFieldData(int /*index*/, const QVariant& /*value*/, + FieldAttrib /*attrib*/) +{ + return false; +} + +/*! + Returns the protocolIdType for the protocol + + The default implementation returns ProtocolIdNone. If a subclass has a + protocolId field it should return the appropriate value e.g. IP protocol + will return ProtocolIdIp, Ethernet will return ProtocolIdEth etc. +*/ +AbstractProtocol::ProtocolIdType AbstractProtocol::protocolIdType() const +{ + return ProtocolIdNone; +} + +/*! + Returns the protocol id of the protocol for the given type + + The default implementation returns 0. If a subclass represents a protocol + which has a particular protocol id, it should return the appropriate value. + If a protocol does not have an id for the given type, it should defer to + the base class. e.g. IGMP will return 2 for ProtocolIdIp, and defer to the + base class for the remaining ProtocolIdTypes; IP will return 0x800 for + ProtocolIdEth type, 0x060603 for ProtocolIdLlc and 0x04 for ProtocolIdIp etc. +*/ +quint32 AbstractProtocol::protocolId(ProtocolIdType /*type*/) const +{ + return 0; +} + +/*! + Returns the protocol id of the payload protocol (the protocol that + immediately follows the current one) + + A subclass which has a protocol id field, can use this to retrieve the + appropriate value +*/ +quint32 AbstractProtocol::payloadProtocolId(ProtocolIdType type) const +{ + quint32 id; + + if (next) + id = next->protocolId(type); + else if (parent) + id = parent->payloadProtocolId(type); + else + id = 0xFFFFFFFF; + + qDebug("%s: payloadProtocolId = 0x%x", __FUNCTION__, id); + return id; +} + +/*! + Returns the protocol's size in bytes + + The default implementation sums up the individual field bit sizes and + returns it. The default implementation calculates the caches the size on + the first invocation and subsequently returns the cached size. + + If the subclass protocol has a varying protocol size, it MUST reimplement + this method, otherwise the default implementation is sufficient. +*/ +int AbstractProtocol::protocolFrameSize(int streamIndex) const +{ + if (protoSize < 0) + { + int bitsize = 0; + + for (int i = 0; i < fieldCount(); i++) + { + if (fieldFlags(i).testFlag(FrameField)) + bitsize += fieldData(i, FieldBitSize, streamIndex).toUInt(); + } + protoSize = (bitsize+7)/8; + } + + qDebug("%s: protoSize = %d", __FUNCTION__, protoSize); + return protoSize; +} + +/*! + Returns the byte offset in the packet where the protocol starts + + This method is useful only for "padding" protocols i.e. protocols which + fill up the remaining space for the user defined packet size e.g. the + PatternPayload protocol +*/ +int AbstractProtocol::protocolFrameOffset(int streamIndex) const +{ + int size = 0; + AbstractProtocol *p = prev; + while (p) + { + size += p->protocolFrameSize(streamIndex); + p = p->prev; + } + + if (parent) + size += parent->protocolFrameOffset(streamIndex); + + qDebug("%s: ofs = %d", __FUNCTION__, size); + return size; +} + +/*! + Returns the size of the payload in bytes. The payload includes all protocols + subsequent to the current + + This method is useful for protocols which need to fill in a payload size field +*/ +int AbstractProtocol::protocolFramePayloadSize(int streamIndex) const +{ + int size = 0; + AbstractProtocol *p = next; + while (p) + { + size += p->protocolFrameSize(streamIndex); + p = p->next; + } + if (parent) + size += parent->protocolFramePayloadSize(streamIndex); + + qDebug("%s: payloadSize = %d", __FUNCTION__, size); + return size; +} + + +/*! + Returns a byte array encoding the protocol (and its fields) which can be + inserted into the stream's frame + + The default implementation forms and returns an ordered concatenation of + the FrameValue of all the 'frame' fields of the protocol also taking care of + fields which are not an integral number of bytes\n +*/ +QByteArray AbstractProtocol::protocolFrameValue(int streamIndex, bool forCksum) const +{ + QByteArray proto, field; + uint bits, lastbitpos = 0; + FieldFlags flags; + + for (int i=0; i < fieldCount() ; i++) + { + flags = fieldFlags(i); + if (flags.testFlag(FrameField)) + { + bits = fieldData(i, FieldBitSize, streamIndex).toUInt(); + if (bits == 0) + continue; + Q_ASSERT(bits > 0); + + if (forCksum && flags.testFlag(CksumField)) + { + field.resize((bits+7)/8); + field.fill('\0'); + } + else + field = fieldData(i, FieldFrameValue, streamIndex).toByteArray(); + qDebug("<<< (%d, %db) %s >>>", proto.size(), lastbitpos, + QString(proto.toHex()).toAscii().constData()); + qDebug(" < %d: (%db/%dB) %s >", i, bits, field.size(), + QString(field.toHex()).toAscii().constData()); + + if (bits == (uint) field.size() * 8) + { + if (lastbitpos == 0) + proto.append(field); + else + { + Q_ASSERT(field.size() > 0); + + char c = proto[proto.size() - 1]; + proto[proto.size() - 1] = + c | ((uchar)field.at(0) >> lastbitpos); + for (int j = 0; j < field.size() - 1; j++) + proto.append(field.at(j) << lastbitpos | + (uchar)field.at(j+1) >> lastbitpos); + proto.append(field.at(field.size() - 1) << lastbitpos); + } + } + else if (bits < (uint) field.size() * 8) + { + uchar c; + uint v; + + v = (field.size()*8) - bits; + + Q_ASSERT(v < 8); + + if (lastbitpos == 0) + { + for (int j = 0; j < field.size(); j++) + { + c = field.at(j) << v; + if ((j+1) < field.size()) + c |= ((uchar)field.at(j+1) >> (8-v)); + proto.append(c); + } + + lastbitpos = (lastbitpos + bits) % 8; + } + else + { + Q_ASSERT(proto.size() > 0); + + for (int j = 0; j < field.size(); j++) + { + uchar d; + + c = field.at(j) << v; + if ((j+1) < field.size()) + c |= ((uchar) field.at(j+1) >> (8-v)); + d = proto[proto.size() - 1]; + proto[proto.size() - 1] = d | ((uchar) c >> lastbitpos); + if (bits > (8*j + (8 - v))) + proto.append(c << (8-lastbitpos)); + } + + lastbitpos = (lastbitpos + bits) % 8; + } + } + else // if (bits > field.size() * 8) + { + qFatal("bitsize more than FrameValue size. skipping..."); + continue; + } + } + } + + return proto; +} + +/*! + Returns true if the protocol varies one or more of its fields at run-time, + false otherwise + + The default implementation returns false. A subclass should reimplement + if it has varying fields e.g. an IP protocol that increments/decrements + the IP address with every packet +*/ +bool AbstractProtocol::isProtocolFrameValueVariable() const +{ + return (protocolFrameVariableCount() > 1); +} + +/*! + Returns true if the protocol varies its size at run-time, false otherwise + + The default implmentation returns false. A subclass should reimplement + if it varies its size at run-time e.g. a Payload protocol for a stream with + incrementing/decrementing frame lengths +*/ +bool AbstractProtocol::isProtocolFrameSizeVariable() const +{ + return false; +} + +/*! + Returns the minimum number of frames required for the protocol to + vary its fields + + This is the lowest common multiple (LCM) of the counts of all the varying + fields in the protocol. Use the AbstractProtocol::lcm() static utility + function to calculate the LCM. + + The default implementation returns 1 implying that the protocol has no + varying fields. A subclass should reimplement if it has varying fields + e.g. an IP protocol that increments/decrements the IP address with + every packet +*/ +int AbstractProtocol::protocolFrameVariableCount() const +{ + return 1; +} + +/*! + Returns true if the payload content for a protocol varies at run-time, + false otherwise + + This is useful for subclasses which have fields dependent on payload content + (e.g. UDP has a checksum field that varies if the payload varies) +*/ +bool AbstractProtocol::isProtocolFramePayloadValueVariable() const +{ + AbstractProtocol *p = next; + + while (p) + { + if (p->isProtocolFrameValueVariable()) + return true; + p = p->next; + } + if (parent && parent->isProtocolFramePayloadValueVariable()) + return true; + + return false; +} + +/*! + Returns true if the payload size for a protocol varies at run-time, + false otherwise + + This is useful for subclasses which have fields dependent on payload size + (e.g. UDP has a checksum field that varies if the payload varies) +*/ +bool AbstractProtocol::isProtocolFramePayloadSizeVariable() const +{ + AbstractProtocol *p = next; + + while (p) + { + if (p->isProtocolFrameSizeVariable()) + return true; + p = p->next; + } + if (parent && parent->isProtocolFramePayloadSizeVariable()) + return true; + + return false; +} + +/*! + Returns true if the payload size for a protocol varies at run-time, + false otherwise + + This is useful for subclasses which have fields dependent on payload size + (e.g. UDP has a checksum field that varies if the payload varies) +*/ +int AbstractProtocol::protocolFramePayloadVariableCount() const +{ + int count = 1; + AbstractProtocol *p = next; + + while (p) + { + if (p->isProtocolFrameValueVariable() + || p->isProtocolFrameSizeVariable()) + count = lcm(count, p->protocolFrameVariableCount()); + p = p->next; + } + if (parent && (parent->isProtocolFramePayloadValueVariable() + || parent->isProtocolFramePayloadSizeVariable())) + count = lcm(count, parent->protocolFramePayloadVariableCount()); + + return false; +} + +/*! + Returns true if the protocol typically contains a payload or other protocols + following it e.g. TCP, UDP have payloads, while ARP, IGMP do not + + The default implementation returns true. If a subclass does not have a + payload, it should set the _hasPayload data member to false +*/ +bool AbstractProtocol::protocolHasPayload() const +{ + return _hasPayload; +} + +/*! + Returns the checksum (of the requested type) of the protocol's contents + + Useful for protocols which have a checksum field + + \note If a subclass uses protocolFrameCksum() from within fieldData() to + derive a cksum field, it MUST handle and return the 'FieldBitSize' + attribute also for that particular field instead of using the default + AbstractProtocol implementation for 'FieldBitSize' - this is required + to prevent infinite recursion +*/ +quint32 AbstractProtocol::protocolFrameCksum(int streamIndex, + CksumType cksumType) const +{ + static int recursionCount = 0; + quint32 cksum = 0xFFFFFFFF; + + recursionCount++; + Q_ASSERT_X(recursionCount < 10, "protocolFrameCksum", "potential infinite recursion - does a protocol checksum field not implement FieldBitSize?"); + + switch(cksumType) + { + case CksumIp: + { + QByteArray fv; + quint16 *ip; + quint32 len, sum = 0; + + fv = protocolFrameValue(streamIndex, true); + ip = (quint16*) fv.constData(); + len = fv.size(); + + while(len > 1) + { + sum += *ip; + if(sum & 0x80000000) + sum = (sum & 0xFFFF) + (sum >> 16); + ip++; + len -= 2; + } + + if (len) + sum += (unsigned short) *(unsigned char *)ip; + + while(sum>>16) + sum = (sum & 0xFFFF) + (sum >> 16); + + cksum = qFromBigEndian((quint16) ~sum); + break; + } + + case CksumTcpUdp: + { + quint16 cks; + quint32 sum = 0; + + cks = protocolFrameCksum(streamIndex, CksumIp); + sum += (quint16) ~cks; + cks = protocolFramePayloadCksum(streamIndex, CksumIp); + sum += (quint16) ~cks; + cks = protocolFrameHeaderCksum(streamIndex, CksumIpPseudo); + sum += (quint16) ~cks; + + while(sum>>16) + sum = (sum & 0xFFFF) + (sum >> 16); + + cksum = (~sum) & 0xFFFF; + break; + } + default: + break; + } + + recursionCount--; + return cksum; +} + +/*! + Returns the checksum of the requested type for the protocol's header + + This is useful for subclasses which needs the header's checksum e.g. TCP/UDP + require a "Pseudo-IP" checksum. The checksum is limited to the specified + scope. + + Currently the default implementation supports only type CksumIpPseudo + + \note The default value for cksumScope is different for + protocolFrameHeaderCksum() and protocolFramePayloadCksum() +*/ +quint32 AbstractProtocol::protocolFrameHeaderCksum(int streamIndex, + CksumType cksumType, CksumScope cksumScope) const +{ + quint32 sum = 0; + quint16 cksum; + AbstractProtocol *p = prev; + + Q_ASSERT(cksumType == CksumIpPseudo); + + while (p) + { + cksum = p->protocolFrameCksum(streamIndex, cksumType); + sum += (quint16) ~cksum; + qDebug("%s: sum = %u, cksum = %u", __FUNCTION__, sum, cksum); + if (cksumScope == CksumScopeAdjacentProtocol) + goto out; + p = p->prev; + } + if (parent) + { + cksum = parent->protocolFrameHeaderCksum(streamIndex, cksumType, + cksumScope); + sum += (quint16) ~cksum; + } + +out: + while(sum>>16) + sum = (sum & 0xFFFF) + (sum >> 16); + + return (quint16) ~sum; +} + +/*! + Returns the checksum of the requested type for the protocol's payload + + This is useful for subclasses which needs the payload's checksum e.g. TCP/UDP + require a IP checksum of the payload (to be combined with other checksums to + derive the final checksum). The checksum is limited to the specified + scope. + + Currently the default implementation supports only type CksumIp + + \note The default value for cksumScope is different for + protocolFrameHeaderCksum() and protocolFramePayloadCksum() +*/ +quint32 AbstractProtocol::protocolFramePayloadCksum(int streamIndex, + CksumType cksumType, CksumScope cksumScope) const +{ + quint32 sum = 0; + quint16 cksum; + AbstractProtocol *p = next; + + Q_ASSERT(cksumType == CksumIp); + + while (p) + { + cksum = p->protocolFrameCksum(streamIndex, cksumType); + sum += (quint16) ~cksum; + if (cksumScope == CksumScopeAdjacentProtocol) + goto out; + p = p->next; + } + + if (parent) + { + cksum = parent->protocolFramePayloadCksum(streamIndex, cksumType, + cksumScope); + sum += (quint16) ~cksum; + } + +out: + while(sum>>16) + sum = (sum & 0xFFFF) + (sum >> 16); + + return (quint16) ~sum; +} + +// Stein's binary GCD algo - from wikipedia +quint64 AbstractProtocol::gcd(quint64 u, quint64 v) +{ + int shift; + + /* GCD(0,x) := x */ + if (u == 0 || v == 0) + return u | v; + + /* Let shift := lg K, where K is the greatest power of 2 + dividing both u and v. */ + for (shift = 0; ((u | v) & 1) == 0; ++shift) { + u >>= 1; + v >>= 1; + } + + while ((u & 1) == 0) + u >>= 1; + + /* From here on, u is always odd. */ + do { + while ((v & 1) == 0) /* Loop X */ + v >>= 1; + + /* Now u and v are both odd, so diff(u, v) is even. + Let u = min(u, v), v = diff(u, v)/2. */ + if (u < v) { + v -= u; + } else { + quint64 diff = u - v; + u = v; + v = diff; + } + v >>= 1; + } while (v != 0); + + return u << shift; +} + +quint64 AbstractProtocol::lcm(quint64 u, quint64 v) +{ +#if 0 + /* LCM(0,x) := x */ + if (u == 0 || v == 0) + return u | v; +#else + /* For our use case, neither u nor v can ever be 0, the minimum + value is 1; we do this correction silently here */ + if (u == 0) u = 1; + if (v == 0) v = 1; + + if (u == 1 || v == 1) + return (u * v); +#endif + + return (u * v)/gcd(u, v); +} + diff --git a/common/abstractprotocol.h b/common/abstractprotocol.h new file mode 100644 index 0000000..84388ae --- /dev/null +++ b/common/abstractprotocol.h @@ -0,0 +1,160 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ABSTRACT_PROTOCOL_H +#define _ABSTRACT_PROTOCOL_H + +#include +#include +#include +#include +#include +#include + +//#include "../rpc/pbhelper.h" +#include "protocol.pb.h" + +#define BASE_BIN (2) +#define BASE_OCT (8) +#define BASE_DEC (10) +#define BASE_HEX (16) + +class StreamBase; +class ProtocolListIterator; + +class AbstractProtocol +{ + template + friend class ComboProtocol; + friend class ProtocolListIterator; + +private: + mutable int _metaFieldCount; + mutable int _frameFieldCount; + mutable int protoSize; + mutable QString protoAbbr; + +protected: + StreamBase *mpStream; //!< Stream that this protocol belongs to + AbstractProtocol *parent; //!< Parent protocol, if any + AbstractProtocol *prev; //!< Protocol preceding this protocol + AbstractProtocol *next; //!< Protocol succeeding this protocol + + //! Is protocol typically followed by payload or another protocol + bool _hasPayload; + +public: + //! Properties of a field, can be OR'd + enum FieldFlag { + FrameField = 0x1, //!< field appears in frame content + MetaField = 0x2, //!< field does not appear in frame, is meta data + CksumField = 0x4 //!< field is a checksum and appears in frame content + }; + Q_DECLARE_FLAGS(FieldFlags, FieldFlag); //!< \private abcd + + //! Various attributes of a field + enum FieldAttrib { + FieldName, //!< name + FieldValue, //!< value in host byte order (user editable) + FieldTextValue, //!< value as text + FieldFrameValue, //!< frame encoded value in network byte order + FieldBitSize, //!< size in bits + }; + + //! Supported Protocol Id types + enum ProtocolIdType { + ProtocolIdNone, //!< Marker representing non-existent protocol id + ProtocolIdLlc, //!< LLC (802.2) + ProtocolIdEth, //!< Ethernet II + ProtocolIdIp, //!< IP + ProtocolIdTcpUdp, //!< TCP/UDP Port Number + }; + + //! Supported checksum types + enum CksumType { + CksumIp, //!< Standard IP Checksum + CksumIpPseudo, //!< Standard checksum for Pseudo-IP header + CksumTcpUdp, //!< Standard TCP/UDP checksum including pseudo-IP + + CksumMax //!< Marker for number of cksum types + }; + + //! Supported checksum scopes + enum CksumScope { + CksumScopeAdjacentProtocol, //!< Cksum only the adjacent protocol + CksumScopeAllProtocols, //!< Cksum over all the protocols + }; + + AbstractProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~AbstractProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const = 0; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol) = 0; + + virtual QString name() const; + virtual QString shortName() const; + + virtual ProtocolIdType protocolIdType() const; + virtual quint32 protocolId(ProtocolIdType type) const; + quint32 payloadProtocolId(ProtocolIdType type) const; + + virtual int fieldCount() const; + int metaFieldCount() const; + virtual int frameFieldCount() const; + + virtual FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + QByteArray protocolFrameValue(int streamIndex = 0, + bool forCksum = false) const; + virtual int protocolFrameSize(int streamIndex = 0) const; + int protocolFrameOffset(int streamIndex = 0) const; + int protocolFramePayloadSize(int streamIndex = 0) const; + + virtual bool isProtocolFrameValueVariable() const; + virtual bool isProtocolFrameSizeVariable() const; + virtual int protocolFrameVariableCount() const; + bool isProtocolFramePayloadValueVariable() const; + bool isProtocolFramePayloadSizeVariable() const; + int protocolFramePayloadVariableCount() const; + + bool protocolHasPayload() const; + + virtual quint32 protocolFrameCksum(int streamIndex = 0, + CksumType cksumType = CksumIp) const; + quint32 protocolFrameHeaderCksum(int streamIndex = 0, + CksumType cksumType = CksumIp, + CksumScope cksumScope = CksumScopeAdjacentProtocol) const; + quint32 protocolFramePayloadCksum(int streamIndex = 0, + CksumType cksumType = CksumIp, + CksumScope cksumScope = CksumScopeAllProtocols) const; + + static quint64 lcm(quint64 u, quint64 v); + static quint64 gcd(quint64 u, quint64 v); +}; +Q_DECLARE_OPERATORS_FOR_FLAGS(AbstractProtocol::FieldFlags); + +#endif diff --git a/common/abstractprotocolconfig.h b/common/abstractprotocolconfig.h new file mode 100644 index 0000000..f9fd971 --- /dev/null +++ b/common/abstractprotocolconfig.h @@ -0,0 +1,120 @@ +/* +Copyright (C) 2013-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _ABSTRACT_PROTOCOL_CONFIG_H +#define _ABSTRACT_PROTOCOL_CONFIG_H + +#include + +class AbstractProtocol; + +/*! + Convenience Macro - can be used by loadWidget() methods +*/ +#define uintToHexStr(num, bytes) \ + QString("%1").arg(num, bytes*2, BASE_HEX, QChar('0')) + +class AbstractProtocolConfigForm : public QWidget +{ + Q_OBJECT +public: +/*! + Constructs the widget +*/ + AbstractProtocolConfigForm(QWidget *parent = 0) + : QWidget(parent) + { + // Do nothing! + } + +/*! + Destroys the widget +*/ + virtual ~AbstractProtocolConfigForm() + { + // Do nothing! + } + +/*! + Allocates and returns a new instance of the widget. + + Caller is responsible for freeing up after use. Subclasses MUST implement + this function +*/ + static AbstractProtocolConfigForm* createInstance() + { + return NULL; + } + +/*! + Loads data from the protocol using it's fieldData() method into this + widget. Any conversion to user friendly display/editing formats (e.g. + hex format) SHOULD be done by this method. + + Subclasses MUST implement this function. See the SampleProtocol for + an example +*/ + virtual void loadWidget(AbstractProtocol* /*proto*/) + { + // Do nothing! + } + +/*! + Stores data from this widget into the protocol using the protocol's + setFieldData() method. Field values MUST be converted from any + user friendly display/editing formats (e.g. hex format) to simple + Qt-style integers/strings before passing to setFieldData() + + Subclasses MUST implement this function. See the SampleProtocol for + an example +*/ + virtual void storeWidget(AbstractProtocol* /*proto*/) + { + // Do nothing! + } + +/*! + Convenience Method - can be used by storeWidget() implementations +*/ + uint hexStrToUInt(QString text, bool *ok=NULL) + { + bool isOk; + uint a_uint = text.remove(QChar(' ')).toUInt(&isOk, 16); + + if (ok) + *ok = isOk; + + return a_uint; + } + +/*! + Convenience Method - can be used by storeWidget() implementations +*/ + quint64 hexStrToUInt64(QString text, bool *ok=NULL) + { + bool isOk; + quint64 a_uint = text.remove(QChar(' ')).toULongLong(&isOk, 16); + + if (ok) + *ok = isOk; + + return a_uint; + } +}; + +#endif diff --git a/common/arp.cpp b/common/arp.cpp new file mode 100644 index 0000000..aee20ce --- /dev/null +++ b/common/arp.cpp @@ -0,0 +1,825 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "arp.h" + +#include +#include + +#define uintToMacStr(num) \ + QString("%1").arg(num, 6*2, BASE_HEX, QChar('0')) \ + .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:").toUpper() + +ArpProtocol::ArpProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ + _hasPayload = false; +} + +ArpProtocol::~ArpProtocol() +{ +} + +AbstractProtocol* ArpProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new ArpProtocol(stream, parent); +} + +quint32 ArpProtocol::protocolNumber() const +{ + return OstProto::Protocol::kArpFieldNumber; +} + +void ArpProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::arp)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void ArpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::arp)) + data.MergeFrom(protocol.GetExtension(OstProto::arp)); +} + +QString ArpProtocol::name() const +{ + return QString("Address Resolution Protocol"); +} + +QString ArpProtocol::shortName() const +{ + return QString("ARP"); +} + +/*! + Return the ProtocolIdType for your protocol \n + + If your protocol doesn't have a protocolId field, you don't need to + reimplement this method - the base class implementation will do the + right thing +*/ +#if 0 +AbstractProtocol::ProtocolIdType ArpProtocol::protocolIdType() const +{ + return ProtocolIdIp; +} +#endif + +/*! + Return the protocolId for your protocol based on the 'type' requested \n + + If not all types are valid for your protocol, handle the valid type(s) + and for the remaining fallback to the base class implementation; if your + protocol doesn't have a protocolId at all, you don't need to reimplement + this method - the base class will do the right thing +*/ +quint32 ArpProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdEth: return 0x0806; + default:break; + } + + return AbstractProtocol::protocolId(type); +} + +int ArpProtocol::fieldCount() const +{ + return arp_fieldCount; +} + +AbstractProtocol::FieldFlags ArpProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case arp_hwType: + case arp_protoType: + + case arp_hwAddrLen: + case arp_protoAddrLen: + + case arp_opCode: + + case arp_senderHwAddr: + case arp_senderProtoAddr: + case arp_targetHwAddr: + case arp_targetProtoAddr: + break; + + case arp_senderHwAddrMode: + case arp_senderHwAddrCount: + + case arp_senderProtoAddrMode: + case arp_senderProtoAddrCount: + case arp_senderProtoAddrMask: + + case arp_targetHwAddrMode: + case arp_targetHwAddrCount: + + case arp_targetProtoAddrMode: + case arp_targetProtoAddrCount: + case arp_targetProtoAddrMask: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +QVariant ArpProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case arp_hwType: + { + switch(attrib) + { + case FieldName: + return QString("Hardware Type"); + case FieldValue: + return data.hw_type(); + case FieldTextValue: + return QString("%1").arg(data.hw_type()); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.hw_type(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + + case arp_protoType: + { + switch(attrib) + { + case FieldName: + return QString("Protocol Type"); + case FieldValue: + return data.proto_type(); + case FieldTextValue: + return QString("%1").arg(data.proto_type(), 4, BASE_HEX, + QChar('0')); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.proto_type(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + + case arp_hwAddrLen: + { + switch(attrib) + { + case FieldName: + return QString("Hardware Address Length"); + case FieldValue: + return data.hw_addr_len(); + case FieldTextValue: + return QString("%1").arg(data.hw_addr_len()); + case FieldFrameValue: + return QByteArray(1, (char) data.hw_addr_len()); + default: + break; + } + break; + } + + case arp_protoAddrLen: + { + switch(attrib) + { + case FieldName: + return QString("Protocol Address Length"); + case FieldValue: + return data.proto_addr_len(); + case FieldTextValue: + return QString("%1").arg(data.proto_addr_len()); + case FieldFrameValue: + return QByteArray(1, (char) data.proto_addr_len()); + default: + break; + } + break; + } + + case arp_opCode: + { + switch(attrib) + { + case FieldName: + return QString("Operation Code"); + case FieldValue: + return data.op_code(); + case FieldTextValue: + return QString("%1").arg(data.op_code()); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.op_code(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + + case arp_senderHwAddr: + { + int u; + const int hwAddrStep = 1; + quint64 hwAddr = 0; + + switch (data.sender_hw_addr_mode()) + { + case OstProto::Arp::kFixed: + hwAddr = data.sender_hw_addr(); + break; + case OstProto::Arp::kIncrement: + u = (streamIndex % data.sender_hw_addr_count()) * + hwAddrStep; + hwAddr = data.sender_hw_addr() + u; + break; + case OstProto::Arp::kDecrement: + u = (streamIndex % data.sender_hw_addr_count()) * + hwAddrStep; + hwAddr = data.sender_hw_addr() - u; + break; + default: + qWarning("Unhandled hw_addr_mode %d", + data.sender_hw_addr_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Sender Hardware Address"); + case FieldValue: + return hwAddr; + case FieldTextValue: + return uintToMacStr(hwAddr); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(8); + qToBigEndian((quint64) hwAddr, (uchar*) fv.data()); + fv.remove(0, 2); + return fv; + } + default: + break; + } + break; + } + + case arp_senderProtoAddr: + { + int u; + quint32 subnet, host, protoAddr = 0; + + switch(data.sender_proto_addr_mode()) + { + case OstProto::Arp::kFixedHost: + protoAddr = data.sender_proto_addr(); + break; + case OstProto::Arp::kIncrementHost: + u = streamIndex % data.sender_proto_addr_count(); + subnet = data.sender_proto_addr() + & data.sender_proto_addr_mask(); + host = (((data.sender_proto_addr() + & ~data.sender_proto_addr_mask()) + u) + & ~data.sender_proto_addr_mask()); + protoAddr = subnet | host; + break; + case OstProto::Arp::kDecrementHost: + u = streamIndex % data.sender_proto_addr_count(); + subnet = data.sender_proto_addr() + & data.sender_proto_addr_mask(); + host = (((data.sender_proto_addr() + & ~data.sender_proto_addr_mask()) - u) + & ~data.sender_proto_addr_mask()); + protoAddr = subnet | host; + break; + case OstProto::Arp::kRandomHost: + subnet = data.sender_proto_addr() + & data.sender_proto_addr_mask(); + host = (qrand() & ~data.sender_proto_addr_mask()); + protoAddr = subnet | host; + break; + default: + qWarning("Unhandled sender_proto_addr_mode = %d", + data.sender_proto_addr_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Sender Protocol Address"); + case FieldValue: + return protoAddr; + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian((quint32) protoAddr, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QHostAddress(protoAddr).toString(); + default: + break; + } + break; + } + + case arp_targetHwAddr: + { + int u; + const int hwAddrStep = 1; + quint64 hwAddr = 0; + + switch (data.target_hw_addr_mode()) + { + case OstProto::Arp::kFixed: + hwAddr = data.target_hw_addr(); + break; + case OstProto::Arp::kIncrement: + u = (streamIndex % data.target_hw_addr_count()) * + hwAddrStep; + hwAddr = data.target_hw_addr() + u; + break; + case OstProto::Arp::kDecrement: + u = (streamIndex % data.target_hw_addr_count()) * + hwAddrStep; + hwAddr = data.target_hw_addr() - u; + break; + default: + qWarning("Unhandled hw_addr_mode %d", + data.target_hw_addr_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Target Hardware Address"); + case FieldValue: + return hwAddr; + case FieldTextValue: + return uintToMacStr(hwAddr); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(8); + qToBigEndian((quint64) hwAddr, (uchar*) fv.data()); + fv.remove(0, 2); + return fv; + } + default: + break; + } + break; + } + + case arp_targetProtoAddr: + { + int u; + quint32 subnet, host, protoAddr = 0; + + switch(data.target_proto_addr_mode()) + { + case OstProto::Arp::kFixed: + protoAddr = data.target_proto_addr(); + break; + case OstProto::Arp::kIncrementHost: + u = streamIndex % data.target_proto_addr_count(); + subnet = data.target_proto_addr() + & data.target_proto_addr_mask(); + host = (((data.target_proto_addr() + & ~data.target_proto_addr_mask()) + u) + & ~data.target_proto_addr_mask()); + protoAddr = subnet | host; + break; + case OstProto::Arp::kDecrementHost: + u = streamIndex % data.target_proto_addr_count(); + subnet = data.target_proto_addr() + & data.target_proto_addr_mask(); + host = (((data.target_proto_addr() + & ~data.target_proto_addr_mask()) - u) + & ~data.target_proto_addr_mask()); + protoAddr = subnet | host; + break; + case OstProto::Arp::kRandomHost: + subnet = data.target_proto_addr() + & data.target_proto_addr_mask(); + host = (qrand() & ~data.target_proto_addr_mask()); + protoAddr = subnet | host; + break; + default: + qWarning("Unhandled target_proto_addr_mode = %d", + data.target_proto_addr_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Target Protocol Address"); + case FieldValue: + return protoAddr; + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian((quint32) protoAddr, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QHostAddress(protoAddr).toString(); + default: + break; + } + break; + } + + // Meta fields + case arp_senderHwAddrMode: + switch(attrib) + { + case FieldName: + return QString("Sender Hardware Address Mode"); + case FieldValue: + return data.sender_hw_addr_mode(); + default: + break; + } + break; + case arp_senderHwAddrCount: + switch(attrib) + { + case FieldName: + return QString("Sender Hardware Address Count"); + case FieldValue: + return data.sender_hw_addr_count(); + default: + break; + } + break; + case arp_senderProtoAddrMode: + switch(attrib) + { + case FieldName: + return QString("Sender Protocol Address Mode"); + case FieldValue: + return data.sender_proto_addr_mode(); + default: + break; + } + break; + case arp_senderProtoAddrCount: + switch(attrib) + { + case FieldName: + return QString("Sender Protocol Address Count"); + case FieldValue: + return data.sender_proto_addr_count(); + default: + break; + } + break; + case arp_senderProtoAddrMask: + switch(attrib) + { + case FieldName: + return QString("Sender Protocol Address Mask"); + case FieldValue: + return data.sender_proto_addr_mask(); + default: + break; + } + break; + + case arp_targetHwAddrMode: + switch(attrib) + { + case FieldName: + return QString("Target Hardware Address Mode"); + case FieldValue: + return data.target_hw_addr_mode(); + default: + break; + } + break; + case arp_targetHwAddrCount: + switch(attrib) + { + case FieldName: + return QString("Target Hardware Address Count"); + case FieldValue: + return data.target_hw_addr_count(); + default: + break; + } + break; + case arp_targetProtoAddrMode: + switch(attrib) + { + case FieldName: + return QString("Target Protocol Address Mode"); + case FieldValue: + return data.target_proto_addr_mode(); + default: + break; + } + break; + case arp_targetProtoAddrCount: + switch(attrib) + { + case FieldName: + return QString("Target Protocol Address Count"); + case FieldValue: + return data.target_proto_addr_count(); + default: + break; + } + break; + case arp_targetProtoAddrMask: + switch(attrib) + { + case FieldName: + return QString("Target Protocol Address Mask"); + case FieldValue: + return data.target_proto_addr_mask(); + default: + break; + } + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool ArpProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case arp_hwType: + { + uint hwType = value.toUInt(&isOk); + if (isOk) + data.set_hw_type(hwType); + break; + } + case arp_protoType: + { + uint protoType = value.toUInt(&isOk); + if (isOk) + data.set_proto_type(protoType); + break; + } + case arp_hwAddrLen: + { + uint hwAddrLen = value.toUInt(&isOk); + if (isOk) + data.set_hw_addr_len(hwAddrLen); + break; + } + case arp_protoAddrLen: + { + uint protoAddrLen = value.toUInt(&isOk); + if (isOk) + data.set_proto_addr_len(protoAddrLen); + break; + } + case arp_opCode: + { + uint opCode = value.toUInt(&isOk); + if (isOk) + data.set_op_code(opCode); + break; + } + + case arp_senderHwAddr: + { + quint64 hwAddr = value.toULongLong(&isOk); + if (isOk) + data.set_sender_hw_addr(hwAddr); + break; + } + case arp_senderHwAddrMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.HwAddrMode_IsValid(mode)) + data.set_sender_hw_addr_mode((OstProto::Arp::HwAddrMode) mode); + else + isOk = false; + break; + } + case arp_senderHwAddrCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_sender_hw_addr_count(count); + break; + } + + case arp_senderProtoAddr: + { + uint protoAddr = value.toUInt(&isOk); + if (isOk) + data.set_sender_proto_addr(protoAddr); + break; + } + case arp_senderProtoAddrMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.ProtoAddrMode_IsValid(mode)) + data.set_sender_proto_addr_mode( + (OstProto::Arp::ProtoAddrMode)mode); + else + isOk = false; + break; + } + case arp_senderProtoAddrCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_sender_proto_addr_count(count); + break; + } + case arp_senderProtoAddrMask: + { + uint mask = value.toUInt(&isOk); + if (isOk) + data.set_sender_proto_addr_mask(mask); + break; + } + + case arp_targetHwAddr: + { + quint64 hwAddr = value.toULongLong(&isOk); + if (isOk) + data.set_target_hw_addr(hwAddr); + break; + } + case arp_targetHwAddrMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.HwAddrMode_IsValid(mode)) + data.set_target_hw_addr_mode((OstProto::Arp::HwAddrMode)mode); + else + isOk = false; + break; + } + case arp_targetHwAddrCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_target_hw_addr_count(count); + break; + } + + case arp_targetProtoAddr: + { + uint protoAddr = value.toUInt(&isOk); + if (isOk) + data.set_target_proto_addr(protoAddr); + break; + } + case arp_targetProtoAddrMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.ProtoAddrMode_IsValid(mode)) + data.set_target_proto_addr_mode( + (OstProto::Arp::ProtoAddrMode)mode); + else + isOk = false; + break; + } + case arp_targetProtoAddrCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_target_proto_addr_count(count); + break; + } + case arp_targetProtoAddrMask: + { + uint mask = value.toUInt(&isOk); + if (isOk) + data.set_target_proto_addr_mask(mask); + break; + } + + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +bool ArpProtocol::isProtocolFrameValueVariable() const +{ + if (fieldData(arp_senderHwAddrMode, FieldValue).toUInt() + != uint(OstProto::Arp::kFixed) + || fieldData(arp_senderProtoAddrMode, FieldValue).toUInt() + != uint(OstProto::Arp::kFixed) + || fieldData(arp_targetHwAddrMode, FieldValue).toUInt() + != uint(OstProto::Arp::kFixed) + || fieldData(arp_targetProtoAddrMode, FieldValue).toUInt() + != uint(OstProto::Arp::kFixed)) + return true; + + return false; +} + +int ArpProtocol::protocolFrameVariableCount() const +{ + int count = 1; + + if (fieldData(arp_senderHwAddrMode, FieldValue).toUInt() + != uint(OstProto::Arp::kFixed)) + { + count = AbstractProtocol::lcm(count, + fieldData(arp_senderHwAddrCount, FieldValue).toUInt()); + } + + if (fieldData(arp_senderProtoAddrMode, FieldValue).toUInt() + != uint(OstProto::Arp::kFixed)) + { + count = AbstractProtocol::lcm(count, + fieldData(arp_senderProtoAddrCount, FieldValue).toUInt()); + } + + if (fieldData(arp_targetHwAddrMode, FieldValue).toUInt() + != uint(OstProto::Arp::kFixed)) + { + count = AbstractProtocol::lcm(count, + fieldData(arp_targetHwAddrCount, FieldValue).toUInt()); + } + + if (fieldData(arp_targetProtoAddrMode, FieldValue).toUInt() + != uint(OstProto::Arp::kFixed)) + { + count = AbstractProtocol::lcm(count, + fieldData(arp_targetProtoAddrCount, FieldValue).toUInt()); + } + + return count; +} diff --git a/common/arp.h b/common/arp.h new file mode 100644 index 0000000..6b674f9 --- /dev/null +++ b/common/arp.h @@ -0,0 +1,103 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ARP_H +#define _ARP_H + +#include "abstractprotocol.h" +#include "arp.pb.h" + +/* +Arp Protocol Frame Format - + +------+------+------+------+------+---------+-------+---------+-------+ + | HTYP | PTYP | HLEN | PLEN | OPER | SHA | SPA | THA | TPA | + | (2) | (2) | (1) | (1) | (2) | (6) | (4) | (6) | (4) | + +------+------+------+------+------+---------+-------+---------+-------+ +Figures in brackets represent field width in bytes +*/ + +class ArpProtocol : public AbstractProtocol +{ +public: + enum arpfield + { + // Frame Fields + arp_hwType, + arp_protoType, + + arp_hwAddrLen, + arp_protoAddrLen, + + arp_opCode, + + arp_senderHwAddr, + arp_senderProtoAddr, + arp_targetHwAddr, + arp_targetProtoAddr, + + // Meta Fields + arp_senderHwAddrMode, + arp_senderHwAddrCount, + + arp_senderProtoAddrMode, + arp_senderProtoAddrCount, + arp_senderProtoAddrMask, + + arp_targetHwAddrMode, + arp_targetHwAddrCount, + + arp_targetProtoAddrMode, + arp_targetProtoAddrCount, + arp_targetProtoAddrMask, + + + arp_fieldCount + }; + + ArpProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~ArpProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual bool isProtocolFrameValueVariable() const; + virtual int protocolFrameVariableCount() const; + +private: + OstProto::Arp data; +}; + +#endif diff --git a/common/arp.proto b/common/arp.proto new file mode 100644 index 0000000..12ccebb --- /dev/null +++ b/common/arp.proto @@ -0,0 +1,67 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// ARP Protocol +message Arp { + + enum HwAddrMode { + kFixed = 0; + kIncrement = 1; + kDecrement = 2; + } + + enum ProtoAddrMode { + kFixedHost = 0; + kIncrementHost = 1; + kDecrementHost = 2; + kRandomHost = 3; + } + + optional uint32 hw_type = 1 [default = 1]; + optional uint32 proto_type = 2 [default = 0x800]; + optional uint32 hw_addr_len = 3 [default = 6]; + optional uint32 proto_addr_len = 4 [default = 4]; + optional uint32 op_code = 5 [default = 1]; // 1 => ARP Request + + optional uint64 sender_hw_addr = 6; + optional HwAddrMode sender_hw_addr_mode = 7 [default = kFixed]; + optional uint32 sender_hw_addr_count = 8 [default = 16]; + + optional uint32 sender_proto_addr = 9; + optional ProtoAddrMode sender_proto_addr_mode = 10 [default = kFixedHost]; + optional uint32 sender_proto_addr_count = 11 [default = 16]; + optional fixed32 sender_proto_addr_mask = 12 [default = 0xFFFFFF00]; + + optional uint64 target_hw_addr = 13; + optional HwAddrMode target_hw_addr_mode = 14 [default = kFixed]; + optional uint32 target_hw_addr_count = 15 [default = 16]; + + optional uint32 target_proto_addr = 16; + optional ProtoAddrMode target_proto_addr_mode = 17 [default = kFixedHost]; + optional uint32 target_proto_addr_count = 18 [default = 16]; + optional fixed32 target_proto_addr_mask = 19 [default = 0xFFFFFF00]; +} + +extend Protocol { + optional Arp arp = 300; +} diff --git a/common/arp.ui b/common/arp.ui new file mode 100644 index 0000000..6f4c847 --- /dev/null +++ b/common/arp.ui @@ -0,0 +1,518 @@ + + Arp + + + + 0 + 0 + 528 + 286 + + + + Form + + + + + + + + + + + + Hardware Type + + + hwType + + + + + + + false + + + + + + + Hardware Address Length + + + hwAddrLen + + + + + + + false + + + + + + + Protocol Type + + + protoType + + + + + + + false + + + + + + + Protocol Address Length + + + protoAddrLen + + + + + + + false + + + + + + + + + + + + + + + + Operation Code + + + + + + + + 1 + 0 + + + + true + + + QComboBox::NoInsert + + + + + + + Qt::Horizontal + + + + 161 + 20 + + + + + + + + + + + + + + false + + + + + + Qt::Horizontal + + + + 101 + 20 + + + + + + + + Address + + + + + + + Mode + + + + + + + Count + + + + + + + Mask + + + + + + + Sender Hardware + + + senderHwAddr + + + + + + + >HH HH HH HH HH HH; + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Fixed + + + + + Increment + + + + + Decrement + + + + + + + + false + + + + 255 + 0 + + + + + + + + + + + Sender Protocol + + + senderProtoAddr + + + + + + + 009.009.009.009; + + + ... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Fixed + + + + + Increment Host + + + + + Decrement Host + + + + + Random Host + + + + + + + + false + + + + 255 + 0 + + + + + + + + + + + false + + + 009.009.009.009; + + + ... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Target Hardware + + + targetHwAddr + + + + + + + + 120 + 0 + + + + >HH HH HH HH HH HH; + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Fixed + + + + + Increment + + + + + Decrement + + + + + + + + false + + + + 255 + 0 + + + + + + + 0 + + + + + + + Target Protocol + + + targetProtoAddr + + + + + + + 000.000.000.000; + + + ... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Fixed + + + + + Increment Host + + + + + Decrement Host + + + + + Random Host + + + + + + + + false + + + + 255 + 0 + + + + + + + + + + + false + + + 009.009.009.009; + + + ... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Qt::Vertical + + + + 20 + 61 + + + + + + + + + IntComboBox + QComboBox +
intcombobox.h
+
+
+ + hwType + protoType + hwAddrLen + protoAddrLen + senderHwAddr + senderHwAddrMode + senderHwAddrCount + senderProtoAddr + senderProtoAddrMode + senderProtoAddrCount + senderProtoAddrMask + targetHwAddr + targetHwAddrMode + targetHwAddrCount + targetProtoAddr + targetProtoAddrMode + targetProtoAddrCount + targetProtoAddrMask + + + +
diff --git a/common/arpconfig.cpp b/common/arpconfig.cpp new file mode 100644 index 0000000..e24dfe4 --- /dev/null +++ b/common/arpconfig.cpp @@ -0,0 +1,270 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "arpconfig.h" +#include "arp.h" + +#include + +ArpConfigForm::ArpConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); + + opCodeCombo->setValidator(new QIntValidator(0, 0xFFFF, this)); + opCodeCombo->addItem(1, "ARP Request"); + opCodeCombo->addItem(2, "ARP Reply"); + + connect(senderHwAddrMode, SIGNAL(currentIndexChanged(int)), + SLOT(on_senderHwAddrMode_currentIndexChanged(int))); + connect(senderProtoAddrMode, SIGNAL(currentIndexChanged(int)), + SLOT(on_senderProtoAddrMode_currentIndexChanged(int))); + connect(targetHwAddrMode, SIGNAL(currentIndexChanged(int)), + SLOT(on_targetHwAddrMode_currentIndexChanged(int))); + connect(targetProtoAddrMode, SIGNAL(currentIndexChanged(int)), + SLOT(on_targetProtoAddrMode_currentIndexChanged(int))); +} + +ArpConfigForm::~ArpConfigForm() +{ +} + +ArpConfigForm* ArpConfigForm::createInstance() +{ + return new ArpConfigForm; +} + +void ArpConfigForm::loadWidget(AbstractProtocol *proto) +{ + hwType->setText( + proto->fieldData( + ArpProtocol::arp_hwType, + AbstractProtocol::FieldValue + ).toString()); + protoType->setText(uintToHexStr( + proto->fieldData( + ArpProtocol::arp_protoType, + AbstractProtocol::FieldValue + ).toUInt(), 2)); + hwAddrLen->setText( + proto->fieldData( + ArpProtocol::arp_hwAddrLen, + AbstractProtocol::FieldValue + ).toString()); + protoAddrLen->setText( + proto->fieldData( + ArpProtocol::arp_protoAddrLen, + AbstractProtocol::FieldValue + ).toString()); + + opCodeCombo->setValue( + proto->fieldData( + ArpProtocol::arp_opCode, + AbstractProtocol::FieldValue + ).toUInt()); + + senderHwAddr->setText(uintToHexStr( + proto->fieldData( + ArpProtocol::arp_senderHwAddr, + AbstractProtocol::FieldValue + ).toULongLong(), 6)); + senderHwAddrMode->setCurrentIndex( + proto->fieldData( + ArpProtocol::arp_senderHwAddrMode, + AbstractProtocol::FieldValue + ).toUInt()); + senderHwAddrCount->setText( + proto->fieldData( + ArpProtocol::arp_senderHwAddrCount, + AbstractProtocol::FieldValue + ).toString()); + + senderProtoAddr->setText(QHostAddress( + proto->fieldData( + ArpProtocol::arp_senderProtoAddr, + AbstractProtocol::FieldValue + ).toUInt()).toString()); + senderProtoAddrMode->setCurrentIndex( + proto->fieldData( + ArpProtocol::arp_senderProtoAddrMode, + AbstractProtocol::FieldValue + ).toUInt()); + senderProtoAddrCount->setText( + proto->fieldData( + ArpProtocol::arp_senderProtoAddrCount, + AbstractProtocol::FieldValue + ).toString()); + senderProtoAddrMask->setText(QHostAddress( + proto->fieldData( + ArpProtocol::arp_senderProtoAddrMask, + AbstractProtocol::FieldValue + ).toUInt()).toString()); + + targetHwAddr->setText(uintToHexStr( + proto->fieldData( + ArpProtocol::arp_targetHwAddr, + AbstractProtocol::FieldValue + ).toULongLong(), 6)); + targetHwAddrMode->setCurrentIndex( + proto->fieldData( + ArpProtocol::arp_targetHwAddrMode, + AbstractProtocol::FieldValue + ).toUInt()); + targetHwAddrCount->setText( + proto->fieldData( + ArpProtocol::arp_targetHwAddrCount, + AbstractProtocol::FieldValue + ).toString()); + + targetProtoAddr->setText(QHostAddress( + proto->fieldData( + ArpProtocol::arp_targetProtoAddr, + AbstractProtocol::FieldValue + ).toUInt()).toString()); + targetProtoAddrMode->setCurrentIndex( + proto->fieldData( + ArpProtocol::arp_targetProtoAddrMode, + AbstractProtocol::FieldValue + ).toUInt()); + targetProtoAddrCount->setText( + proto->fieldData( + ArpProtocol::arp_targetProtoAddrCount, + AbstractProtocol::FieldValue + ).toString()); + targetProtoAddrMask->setText(QHostAddress( + proto->fieldData( + ArpProtocol::arp_targetProtoAddrMask, + AbstractProtocol::FieldValue + ).toUInt()).toString()); +} + +void ArpConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + ArpProtocol::arp_hwType, + hwType->text()); + proto->setFieldData( + ArpProtocol::arp_protoType, + hexStrToUInt(protoType->text())); + proto->setFieldData( + ArpProtocol::arp_hwAddrLen, + hwAddrLen->text()); + proto->setFieldData( + ArpProtocol::arp_protoAddrLen, + protoAddrLen->text()); + + proto->setFieldData( + ArpProtocol::arp_opCode, + opCodeCombo->currentValue()); + + proto->setFieldData( + ArpProtocol::arp_senderHwAddr, + hexStrToUInt64(senderHwAddr->text())); + proto->setFieldData( + ArpProtocol::arp_senderHwAddrMode, + senderHwAddrMode->currentIndex()); + proto->setFieldData( + ArpProtocol::arp_senderHwAddrCount, + senderHwAddrCount->text()); + + proto->setFieldData( + ArpProtocol::arp_senderProtoAddr, + QHostAddress(senderProtoAddr->text()).toIPv4Address()); + proto->setFieldData( + ArpProtocol::arp_senderProtoAddrMode, + senderProtoAddrMode->currentIndex()); + proto->setFieldData( + ArpProtocol::arp_senderProtoAddrCount, + senderProtoAddrCount->text()); + proto->setFieldData( + ArpProtocol::arp_senderProtoAddrMask, + QHostAddress(senderProtoAddrMask->text()).toIPv4Address()); + + proto->setFieldData( + ArpProtocol::arp_targetHwAddr, + hexStrToUInt64(targetHwAddr->text())); + proto->setFieldData( + ArpProtocol::arp_targetHwAddrMode, + targetHwAddrMode->currentIndex()); + proto->setFieldData( + ArpProtocol::arp_targetHwAddrCount, + targetHwAddrCount->text()); + + proto->setFieldData( + ArpProtocol::arp_targetProtoAddr, + QHostAddress(targetProtoAddr->text()).toIPv4Address()); + proto->setFieldData( + ArpProtocol::arp_targetProtoAddrMode, + targetProtoAddrMode->currentIndex()); + proto->setFieldData( + ArpProtocol::arp_targetProtoAddrCount, + targetProtoAddrCount->text()); + proto->setFieldData( + ArpProtocol::arp_targetProtoAddrMask, + QHostAddress(targetProtoAddrMask->text()).toIPv4Address()); +} + +/* + * ------------ Private Slots -------------- + */ + +void ArpConfigForm::on_senderHwAddrMode_currentIndexChanged(int index) +{ + if (index == OstProto::Arp::kFixed) + senderHwAddrCount->setDisabled(true); + else + senderHwAddrCount->setEnabled(true); +} + +void ArpConfigForm::on_targetHwAddrMode_currentIndexChanged(int index) +{ + if (index == OstProto::Arp::kFixed) + targetHwAddrCount->setDisabled(true); + else + targetHwAddrCount->setEnabled(true); +} + +void ArpConfigForm::on_senderProtoAddrMode_currentIndexChanged(int index) +{ + if (index == OstProto::Arp::kFixedHost) + { + senderProtoAddrCount->setDisabled(true); + senderProtoAddrMask->setDisabled(true); + } + else + { + senderProtoAddrCount->setEnabled(true); + senderProtoAddrMask->setEnabled(true); + } +} + +void ArpConfigForm::on_targetProtoAddrMode_currentIndexChanged(int index) +{ + if (index == OstProto::Arp::kFixedHost) + { + targetProtoAddrCount->setDisabled(true); + targetProtoAddrMask->setDisabled(true); + } + else + { + targetProtoAddrCount->setEnabled(true); + targetProtoAddrMask->setEnabled(true); + } +} + diff --git a/common/arpconfig.h b/common/arpconfig.h new file mode 100644 index 0000000..15aa4bc --- /dev/null +++ b/common/arpconfig.h @@ -0,0 +1,46 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ARP_CONFIG_H +#define _ARP_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_arp.h" + +class ArpConfigForm : + public AbstractProtocolConfigForm, + private Ui::Arp +{ + Q_OBJECT +public: + ArpConfigForm(QWidget *parent = 0); + virtual ~ArpConfigForm(); + + static ArpConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +private slots: + void on_senderHwAddrMode_currentIndexChanged(int index); + void on_senderProtoAddrMode_currentIndexChanged(int index); + void on_targetHwAddrMode_currentIndexChanged(int index); + void on_targetProtoAddrMode_currentIndexChanged(int index); +}; + +#endif diff --git a/common/arppdml.cpp b/common/arppdml.cpp new file mode 100644 index 0000000..9580db7 --- /dev/null +++ b/common/arppdml.cpp @@ -0,0 +1,41 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "arppdml.h" + +#include "arp.pb.h" + +PdmlArpProtocol::PdmlArpProtocol() +{ + ostProtoId_ = OstProto::Protocol::kArpFieldNumber; + + fieldMap_.insert("arp.opcode", OstProto::Arp::kOpCodeFieldNumber); + fieldMap_.insert("arp.src.hw_mac", OstProto::Arp::kSenderHwAddrFieldNumber); + fieldMap_.insert("arp.src.proto_ipv4", + OstProto::Arp::kSenderProtoAddrFieldNumber); + fieldMap_.insert("arp.dst.hw_mac", OstProto::Arp::kTargetHwAddrFieldNumber); + fieldMap_.insert("arp.dst.proto_ipv4", + OstProto::Arp::kTargetProtoAddrFieldNumber); +} + +PdmlProtocol* PdmlArpProtocol::createInstance() +{ + return new PdmlArpProtocol(); +} + diff --git a/common/arppdml.h b/common/arppdml.h new file mode 100644 index 0000000..039ed9b --- /dev/null +++ b/common/arppdml.h @@ -0,0 +1,34 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ARP_PDML_H +#define _ARP_PDML_H + +#include "pdmlprotocol.h" + +class PdmlArpProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + +protected: + PdmlArpProtocol(); +}; + +#endif diff --git a/common/comboprotocol.h b/common/comboprotocol.h new file mode 100644 index 0000000..97fe960 --- /dev/null +++ b/common/comboprotocol.h @@ -0,0 +1,190 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _COMBO_PROTOCOL_H +#define _COMBO_PROTOCOL_H + +#include "abstractprotocol.h" + +template +class ComboProtocol : public AbstractProtocol +{ +protected: + ProtoA *protoA; + ProtoB *protoB; + +public: + ComboProtocol(StreamBase *stream, AbstractProtocol *parent = 0) + : AbstractProtocol(stream, parent) + { + protoA = new ProtoA(stream, this); + protoB = new ProtoB(stream, this); + protoA->next = protoB; + protoB->prev = protoA; + + qDebug("%s: protoNumber = %d, %p <--> %p", __FUNCTION__, + protoNumber, protoA, protoB); + } + + virtual ~ComboProtocol() + { + delete protoA; + delete protoB; + } + + static ComboProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent) + { + return new ComboProtocol(stream, parent); + } + + virtual quint32 protocolNumber() const + { + return protoNumber; + } + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const + { + protoA->protoDataCopyInto(protocol); + protoB->protoDataCopyInto(protocol); + protocol.mutable_protocol_id()->set_id(protocolNumber()); + } + + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol) + { + if (protocol.protocol_id().id() == protocolNumber()) + { + OstProto::Protocol proto; + + // NOTE: To use protoX->protoDataCopyFrom() we need to arrange + // so that it sees its own protocolNumber() - but since the + // input param 'protocol' is 'const', we work on a copy + proto.CopyFrom(protocol); + + proto.mutable_protocol_id()->set_id(protoA->protocolNumber()); + protoA->protoDataCopyFrom(proto); + + proto.mutable_protocol_id()->set_id(protoB->protocolNumber()); + protoB->protoDataCopyFrom(proto); + } + } + + virtual QString name() const + { + return protoA->name() + "/" + protoB->name(); + } + virtual QString shortName() const + { + return protoA->shortName() + "/" + protoB->shortName(); + } + + virtual ProtocolIdType protocolIdType() const + { + return protoB->protocolIdType(); + } + + virtual quint32 protocolId(ProtocolIdType type) const + { + return protoA->protocolId(type); + } + //quint32 payloadProtocolId(ProtocolIdType type) const; + + virtual int fieldCount() const + { + return protoA->fieldCount() + protoB->fieldCount(); + } + //virtual int metaFieldCount() const; + //int frameFieldCount() const; + + virtual FieldFlags fieldFlags(int index) const + { + int cnt = protoA->fieldCount(); + + if (index < cnt) + return protoA->fieldFlags(index); + else + return protoB->fieldFlags(index - cnt); + } + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const + { + int cnt = protoA->fieldCount(); + + if (index < cnt) + return protoA->fieldData(index, attrib, streamIndex); + else + return protoB->fieldData(index - cnt, attrib, streamIndex); + } + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue) + { + int cnt = protoA->fieldCount(); + + if (index < cnt) + return protoA->setFieldData(index, value, attrib); + else + return protoB->setFieldData(index - cnt, value, attrib); + } + +#if 0 + QByteArray protocolFrameValue(int streamIndex = 0, + bool forCksum = false) const; + virtual int protocolFrameSize() const; + int protocolFrameOffset() const; + int protocolFramePayloadSize() const; +#endif + + virtual bool isProtocolFrameValueVariable() const + { + return (protoA->isProtocolFrameValueVariable() + || protoB->isProtocolFrameValueVariable()); + } + + virtual bool isProtocolFrameSizeVariable() const + { + return (protoA->isProtocolFrameSizeVariable() + || protoB->isProtocolFrameSizeVariable()); + } + virtual int protocolFrameVariableCount() const + { + return AbstractProtocol::lcm( + protoA->protocolFrameVariableCount(), + protoB->protocolFrameVariableCount()); + } + + virtual quint32 protocolFrameCksum(int streamIndex = 0, + CksumType cksumType = CksumIp) const + { + // For a Pseudo IP cksum, we assume it is the succeeding protocol + // that is requesting it and hence return protoB's cksum; + if (cksumType == CksumIpPseudo) + return protoB->protocolFrameCksum(streamIndex, cksumType); + + return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); + } +#if 0 + quint32 protocolFrameHeaderCksum(int streamIndex = 0, + CksumType cksumType = CksumIp) const; + quint32 protocolFramePayloadCksum(int streamIndex = 0, + CksumType cksumType = CksumIp) const; +#endif + template friend class ComboProtocolConfigForm; +}; + +#endif diff --git a/common/comboprotocolconfig.h b/common/comboprotocolconfig.h new file mode 100644 index 0000000..d77627f --- /dev/null +++ b/common/comboprotocolconfig.h @@ -0,0 +1,100 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _COMBO_PROTOCOL_CONFIG_H +#define _COMBO_PROTOCOL_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "comboprotocol.h" + +template +class ComboProtocolConfigForm : + public AbstractProtocolConfigForm +{ +public: + ComboProtocolConfigForm(QWidget *parent = 0) + : AbstractProtocolConfigForm(parent) + { + QVBoxLayout *layout = new QVBoxLayout; + + formA = new FormA(this); + formB = new FormB(this); + + layout->addWidget(formA); + layout->addWidget(formB); + layout->setSpacing(0); + layout->setContentsMargins(0, 0, 0, 0); + setLayout(layout); + + qDebug("%s: protoNumber = %d, %p <--> %p", __FUNCTION__, + protoNumber, formA, formB); + } + + virtual ~ComboProtocolConfigForm() + { + formA->setParent(0); + formB->setParent(0); + + delete formA; + delete formB; + } + + static ComboProtocolConfigForm* createInstance() + { + return new ComboProtocolConfigForm; + } + + virtual void loadWidget(AbstractProtocol *proto) + { + class ComboProtocol *comboProto = + dynamic_cast*>(proto); + + Q_ASSERT_X(comboProto != NULL, + QString("ComboProtocolConfigForm{%1}::loadWidget()") + .arg(protoNumber).toAscii().constData(), + QString("Protocol{%1} is not a instance of ComboProtocol") + .arg(proto->protocolNumber()).toAscii().constData()); + + formA->loadWidget(comboProto->protoA); + formB->loadWidget(comboProto->protoB); + } + virtual void storeWidget(AbstractProtocol *proto) + { + class ComboProtocol *comboProto = + dynamic_cast*>(proto); + + Q_ASSERT_X(comboProto != NULL, + QString("ComboProtocolConfigForm{%1}::loadWidget()") + .arg(protoNumber).toAscii().constData(), + QString("Protocol{%1} is not a instance of ComboProtocol") + .arg(proto->protocolNumber()).toAscii().constData()); + + formA->storeWidget(comboProto->protoA); + formB->storeWidget(comboProto->protoB); + } + +protected: + FormA *formA; + FormB *formB; +}; + +#endif diff --git a/common/crc32c.cpp b/common/crc32c.cpp new file mode 100644 index 0000000..b4206f8 --- /dev/null +++ b/common/crc32c.cpp @@ -0,0 +1,134 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +/******************************************************************* +** IMPORTANT NOTE: +** This code is from RFC 4960 Stream Control Transmission Protocol +** It has been modified suitably while keeping the algorithm intact. +********************************************************************/ + +#include "crc32c.h" + +#define CRC32C(c,d) (c=(c>>8)^crc_c[(c^(d))&0xFF]) + +quint32 crc_c[256] = +{ + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, + 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, + 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, + 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, + 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, + 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, + 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, + 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, + 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, + 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, + 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, + 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, + 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, + 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, + 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, + 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, + 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, + 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, + 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, + 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, + 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, + 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, + 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, + 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, + 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, + 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, + 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, + 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, + 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, + 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, + 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, + 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, + 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, + 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, + 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, + 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, + 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, + 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, + 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, + 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, + 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, + 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, + 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, + 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, + 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, + 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, + 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, + 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, + 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, + 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, + 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, + 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L, +}; + +quint32 checksumCrc32C(quint8 *buffer, uint length) +{ + uint i; + quint32 crc32 = ~0L; + quint32 result; + quint8 byte0,byte1,byte2,byte3; + + for (i = 0; i < length; i++) { + CRC32C(crc32, buffer[i]); + } + + result = ~crc32; + + /* result now holds the negated polynomial remainder; + * since the table and algorithm is "reflected" [williams95]. + * That is, result has the same value as if we mapped the message + * to a polynomial, computed the host-bit-order polynomial + * remainder, performed final negation, then did an end-for-end + * bit-reversal. + * Note that a 32-bit bit-reversal is identical to four inplace + * 8-bit reversals followed by an end-for-end byteswap. + * In other words, the bytes of each bit are in the right order, + * but the bytes have been byteswapped. So we now do an explicit + * byteswap. On a little-endian machine, this byteswap and + * the final ntohl cancel out and could be elided. + */ + + byte0 = result & 0xff; + byte1 = (result>>8) & 0xff; + byte2 = (result>>16) & 0xff; + byte3 = (result>>24) & 0xff; + crc32 = ((byte0 << 24) | + (byte1 << 16) | + (byte2 << 8) | + byte3); + return ( crc32 ); +} diff --git a/common/crc32c.h b/common/crc32c.h new file mode 100644 index 0000000..84cdc76 --- /dev/null +++ b/common/crc32c.h @@ -0,0 +1,23 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include + +quint32 checksumCrc32C(quint8 *buffer, uint length); + diff --git a/common/dot2llc.h b/common/dot2llc.h new file mode 100644 index 0000000..b858914 --- /dev/null +++ b/common/dot2llc.h @@ -0,0 +1,30 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _DOT2_LLC_H +#define _DOT2_LLC_H + +#include "comboprotocol.h" +#include "dot3.h" +#include "llc.h" + +typedef ComboProtocol Dot2LlcProtocol; + +#endif diff --git a/common/dot2llc.proto b/common/dot2llc.proto new file mode 100644 index 0000000..8223650 --- /dev/null +++ b/common/dot2llc.proto @@ -0,0 +1,33 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; +import "dot3.proto"; +import "llc.proto"; + +package OstProto; + +// 802.2 LLC +message Dot2Llc { + // Empty since this is a 'combo' protocol +} + +extend Protocol { + optional Dot2Llc dot2Llc = 206; +} diff --git a/common/dot2llcconfig.h b/common/dot2llcconfig.h new file mode 100644 index 0000000..76a5b24 --- /dev/null +++ b/common/dot2llcconfig.h @@ -0,0 +1,36 @@ +/* +Copyright (C) 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _DOT2_LLC_CONFIG_H +#define _DOT2_LLC_CONFIG_H + +#include "comboprotocolconfig.h" + +#include "dot3config.h" +#include "llcconfig.h" +#include "dot3.h" +#include "llc.h" + +typedef ComboProtocolConfigForm < + OstProto::Protocol::kDot2LlcFieldNumber, + Dot3ConfigForm, LlcConfigForm, + Dot3Protocol, LlcProtocol + > Dot2LlcConfigForm; + +#endif diff --git a/common/dot2snap.h b/common/dot2snap.h new file mode 100644 index 0000000..0da586a --- /dev/null +++ b/common/dot2snap.h @@ -0,0 +1,30 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _DOT2_SNAP_H +#define _DOT2_SNAP_H + +#include "comboprotocol.h" +#include "dot2llc.h" +#include "snap.h" + +typedef ComboProtocol Dot2SnapProtocol; + +#endif diff --git a/common/dot2snap.proto b/common/dot2snap.proto new file mode 100644 index 0000000..d49059f --- /dev/null +++ b/common/dot2snap.proto @@ -0,0 +1,31 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// 802.2 SNAP +message Dot2Snap { + // Empty since this is a 'combo' protocol +} + +extend Protocol { + optional Dot2Snap dot2Snap = 207; +} diff --git a/common/dot2snapconfig.h b/common/dot2snapconfig.h new file mode 100644 index 0000000..32a8d99 --- /dev/null +++ b/common/dot2snapconfig.h @@ -0,0 +1,36 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _DOT2_SNAP_CONFIG_H +#define _DOT2_SNAP_CONFIG_H + +#include "comboprotocol.h" + +#include "dot2llcconfig.h" +#include "snapconfig.h" +#include "dot2llc.h" +#include "snap.h" + +typedef ComboProtocolConfigForm < + OstProto::Protocol::kDot2SnapFieldNumber, + Dot2LlcConfigForm, SnapConfigForm, + Dot2LlcProtocol, SnapProtocol + > Dot2SnapConfigForm; + +#endif diff --git a/common/dot3.cpp b/common/dot3.cpp new file mode 100644 index 0000000..68ef9a6 --- /dev/null +++ b/common/dot3.cpp @@ -0,0 +1,196 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "dot3.h" + +Dot3Protocol::Dot3Protocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +Dot3Protocol::~Dot3Protocol() +{ +} + +AbstractProtocol* Dot3Protocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new Dot3Protocol(stream, parent); +} + +quint32 Dot3Protocol::protocolNumber() const +{ + return OstProto::Protocol::kDot3FieldNumber; +} + +void Dot3Protocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::dot3)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void Dot3Protocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::dot3)) + data.MergeFrom(protocol.GetExtension(OstProto::dot3)); +} + +QString Dot3Protocol::name() const +{ + return QString("802.3"); +} + +QString Dot3Protocol::shortName() const +{ + return QString("802.3"); +} + +int Dot3Protocol::fieldCount() const +{ + return dot3_fieldCount; +} + +AbstractProtocol::FieldFlags Dot3Protocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case dot3_length: + break; + + // Meta fields + case dot3_is_override_length: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + break; + } + + return flags; +} + +QVariant Dot3Protocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case dot3_length: + switch(attrib) + { + case FieldName: + return QString("Length"); + case FieldValue: + { + quint16 len = data.is_override_length() ? + data.length() : protocolFramePayloadSize(streamIndex); + return len; + } + case FieldTextValue: + { + quint16 len = data.is_override_length() ? + data.length() : protocolFramePayloadSize(streamIndex); + + return QString("%1").arg(len); + } + case FieldFrameValue: + { + quint16 len = data.is_override_length() ? + data.length() : protocolFramePayloadSize(streamIndex); + QByteArray fv; + + fv.resize(2); + qToBigEndian(len, (uchar*) fv.data()); + return fv; + } + case FieldBitSize: + return 16; + default: + break; + } + break; + + // Meta fields + case dot3_is_override_length: + { + switch(attrib) + { + case FieldValue: + return data.is_override_length(); + default: + break; + } + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool Dot3Protocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + return false; + + switch (index) + { + case dot3_length: + { + uint len = value.toUInt(&isOk); + if (isOk) + data.set_length(len); + break; + } + case dot3_is_override_length: + { + bool ovr = value.toBool(); + data.set_is_override_length(ovr); + isOk = true; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + return isOk; +} + +bool Dot3Protocol::isProtocolFrameValueVariable() const +{ + return isProtocolFramePayloadSizeVariable(); +} + +int Dot3Protocol::protocolFrameVariableCount() const +{ + return protocolFramePayloadVariableCount(); +} diff --git a/common/dot3.h b/common/dot3.h new file mode 100644 index 0000000..ecade93 --- /dev/null +++ b/common/dot3.h @@ -0,0 +1,67 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _DOT3_H +#define _DOT3_H + +#include "abstractprotocol.h" +#include "dot3.pb.h" + +class Dot3Protocol : public AbstractProtocol +{ +public: + enum Dot3field + { + dot3_length, + + // Meta-fields + dot3_is_override_length, + + dot3_fieldCount + }; + + Dot3Protocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~Dot3Protocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual bool isProtocolFrameValueVariable() const; + virtual int protocolFrameVariableCount() const; + +private: + OstProto::Dot3 data; +}; + +#endif diff --git a/common/dot3.proto b/common/dot3.proto new file mode 100644 index 0000000..f20f120 --- /dev/null +++ b/common/dot3.proto @@ -0,0 +1,32 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// 802.3 +message Dot3 { + optional bool is_override_length = 2; + optional uint32 length = 1; +} + +extend Protocol { + optional Dot3 dot3 = 201; +} diff --git a/common/dot3.ui b/common/dot3.ui new file mode 100644 index 0000000..5631eaf --- /dev/null +++ b/common/dot3.ui @@ -0,0 +1,86 @@ + + dot3 + + + + 0 + 0 + 181 + 98 + + + + Form + + + + + + 802.3 + + + + + + Length + + + + + + + false + + + + + + + + + + Qt::Horizontal + + + + 16 + 54 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + cbOverrideLength + toggled(bool) + leLength + setEnabled(bool) + + + 55 + 39 + + + 84 + 43 + + + + + diff --git a/common/dot3config.cpp b/common/dot3config.cpp new file mode 100644 index 0000000..d17094e --- /dev/null +++ b/common/dot3config.cpp @@ -0,0 +1,63 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "dot3config.h" +#include "dot3.h" +#include + +Dot3ConfigForm::Dot3ConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); + leLength->setValidator(new QIntValidator(0, 16384, this)); +} + +Dot3ConfigForm::~Dot3ConfigForm() +{ +} + +Dot3ConfigForm* Dot3ConfigForm::createInstance() +{ + return new Dot3ConfigForm; +} + +void Dot3ConfigForm::loadWidget(AbstractProtocol *proto) +{ + cbOverrideLength->setChecked( + proto->fieldData( + Dot3Protocol::dot3_is_override_length, + AbstractProtocol::FieldValue + ).toBool()); + leLength->setText( + proto->fieldData( + Dot3Protocol::dot3_length, + AbstractProtocol::FieldValue + ).toString()); +} + +void Dot3ConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + Dot3Protocol::dot3_is_override_length, + cbOverrideLength->isChecked()); + proto->setFieldData( + Dot3Protocol::dot3_length, + leLength->text()); +} + diff --git a/common/dot3config.h b/common/dot3config.h new file mode 100644 index 0000000..44e3e7e --- /dev/null +++ b/common/dot3config.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _DOT3_CONFIG_H +#define _DOT3_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_dot3.h" + +class Dot3ConfigForm : + public AbstractProtocolConfigForm, + private Ui::dot3 +{ + Q_OBJECT +public: + Dot3ConfigForm(QWidget *parent = 0); + virtual ~Dot3ConfigForm(); + + static Dot3ConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +}; + +#endif diff --git a/common/eth2.cpp b/common/eth2.cpp new file mode 100644 index 0000000..63f361a --- /dev/null +++ b/common/eth2.cpp @@ -0,0 +1,188 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "eth2.h" + +Eth2Protocol::Eth2Protocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +Eth2Protocol::~Eth2Protocol() +{ +} + +AbstractProtocol* Eth2Protocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new Eth2Protocol(stream, parent); +} + +quint32 Eth2Protocol::protocolNumber() const +{ + return OstProto::Protocol::kEth2FieldNumber; +} + +void Eth2Protocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::eth2)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void Eth2Protocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::eth2)) + data.MergeFrom(protocol.GetExtension(OstProto::eth2)); +} + +QString Eth2Protocol::name() const +{ + return QString("Ethernet II"); +} + +QString Eth2Protocol::shortName() const +{ + return QString("Eth II"); +} + +AbstractProtocol::ProtocolIdType Eth2Protocol::protocolIdType() const +{ + return ProtocolIdEth; +} + +int Eth2Protocol::fieldCount() const +{ + return eth2_fieldCount; +} + +AbstractProtocol::FieldFlags Eth2Protocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case eth2_type: + break; + + case eth2_is_override_type: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + break; + } + + return flags; +} + +QVariant Eth2Protocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case eth2_type: + { + switch(attrib) + { + case FieldName: + return QString("Type"); + case FieldValue: + { + quint16 type = data.is_override_type() ? + data.type() : payloadProtocolId(ProtocolIdEth); + return type; + } + case FieldTextValue: + { + quint16 type = data.is_override_type() ? + data.type() : payloadProtocolId(ProtocolIdEth); + return QString("0x%1").arg(type, 4, BASE_HEX, QChar('0')); + } + case FieldFrameValue: + { + QByteArray fv; + quint16 type = data.is_override_type() ? + data.type() : payloadProtocolId(ProtocolIdEth); + fv.resize(2); + qToBigEndian((quint16) type, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + + // Meta fields + case eth2_is_override_type: + { + switch(attrib) + { + case FieldValue: + return data.is_override_type(); + default: + break; + } + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool Eth2Protocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + return false; + + switch (index) + { + case eth2_type: + { + uint type = value.toUInt(&isOk); + if (isOk) + data.set_type(type); + break; + } + case eth2_is_override_type: + { + bool ovr = value.toBool(); + data.set_is_override_type(ovr); + isOk = true; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + return isOk; +} diff --git a/common/eth2.h b/common/eth2.h new file mode 100644 index 0000000..5846d14 --- /dev/null +++ b/common/eth2.h @@ -0,0 +1,66 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ETH2_H +#define _ETH2_H + +#include "abstractprotocol.h" + +#include "eth2.pb.h" + + +class Eth2Protocol : public AbstractProtocol +{ +public: + enum eth2field + { + eth2_type = 0, + + eth2_is_override_type, + + eth2_fieldCount + }; + + Eth2Protocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~Eth2Protocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual ProtocolIdType protocolIdType() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); +private: + OstProto::Eth2 data; +}; + +#endif diff --git a/common/eth2.proto b/common/eth2.proto new file mode 100644 index 0000000..47db7e7 --- /dev/null +++ b/common/eth2.proto @@ -0,0 +1,33 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Ethernet II +message Eth2 { + optional bool is_override_type = 2; + + optional uint32 type = 1; +} + +extend Protocol { + optional Eth2 eth2 = 200; +} diff --git a/common/eth2.ui b/common/eth2.ui new file mode 100644 index 0000000..a43fb36 --- /dev/null +++ b/common/eth2.ui @@ -0,0 +1,80 @@ + + eth2 + + + + 0 + 0 + 190 + 64 + + + + Form + + + + + + Ethernet Type + + + + + + + false + + + >HH HH; + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + cbOverrideType + toggled(bool) + leType + setEnabled(bool) + + + 98 + 27 + + + 118 + 27 + + + + + diff --git a/common/eth2config.cpp b/common/eth2config.cpp new file mode 100644 index 0000000..7cf3bd5 --- /dev/null +++ b/common/eth2config.cpp @@ -0,0 +1,63 @@ +/* +Copyright (C) 2010,2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "eth2config.h" +#include "eth2.h" + +Eth2ConfigForm::Eth2ConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); +} + +Eth2ConfigForm::~Eth2ConfigForm() +{ +} + +Eth2ConfigForm* Eth2ConfigForm::createInstance() +{ + return new Eth2ConfigForm; +} + +void Eth2ConfigForm::loadWidget(AbstractProtocol *proto) +{ + cbOverrideType->setChecked( + proto->fieldData( + Eth2Protocol::eth2_is_override_type, + AbstractProtocol::FieldValue + ).toBool()); + leType->setText(uintToHexStr( + proto->fieldData( + Eth2Protocol::eth2_type, + AbstractProtocol::FieldValue + ).toUInt(), 2)); +} + +void Eth2ConfigForm::storeWidget(AbstractProtocol *proto) +{ + bool isOk; + + proto->setFieldData( + Eth2Protocol::eth2_is_override_type, + cbOverrideType->isChecked()); + proto->setFieldData( + Eth2Protocol::eth2_type, + leType->text().remove(QChar(' ')).toUInt(&isOk, BASE_HEX)); +} + diff --git a/common/eth2config.h b/common/eth2config.h new file mode 100644 index 0000000..918a855 --- /dev/null +++ b/common/eth2config.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010,2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ETH2_CONFIG_H +#define _ETH2_CONFIG_H + +#include "abstractprotocolconfig.h" + +#include "eth2.pb.h" +#include "ui_eth2.h" + +class Eth2ConfigForm : + public AbstractProtocolConfigForm, + public Ui::eth2 +{ + Q_OBJECT +public: + Eth2ConfigForm(QWidget *parent = 0); + virtual ~Eth2ConfigForm(); + + static Eth2ConfigForm* createInstance(); + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +}; +#endif diff --git a/common/eth2pdml.cpp b/common/eth2pdml.cpp new file mode 100644 index 0000000..8bb8d17 --- /dev/null +++ b/common/eth2pdml.cpp @@ -0,0 +1,124 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "eth2pdml.h" + +#include "dot3.pb.h" +#include "eth2.pb.h" +#include "mac.pb.h" +#include "vlan.pb.h" + +PdmlEthProtocol::PdmlEthProtocol() +{ + ostProtoId_ = OstProto::Protocol::kMacFieldNumber; + + fieldMap_.insert("eth.dst", OstProto::Mac::kDstMacFieldNumber); + fieldMap_.insert("eth.src", OstProto::Mac::kSrcMacFieldNumber); +} + +PdmlProtocol* PdmlEthProtocol::createInstance() +{ + return new PdmlEthProtocol(); +} + +void PdmlEthProtocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol* /*pbProto*/, OstProto::Stream *stream) +{ + if (name == "eth.vlan.tpid") + { + bool isOk; + + uint tpid = attributes.value("value").toString() + .toUInt(&isOk, kBaseHex); + + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kVlanFieldNumber); + + OstProto::Vlan *vlan = proto->MutableExtension(OstProto::vlan); + + vlan->set_tpid(tpid); + vlan->set_is_override_tpid(true); + } + else if (name == "eth.vlan.id") + { + bool isOk; + + uint tag = attributes.value("unmaskedvalue").isEmpty() ? + attributes.value("value").toString().toUInt(&isOk, kBaseHex) : + attributes.value("unmaskedvalue").toString().toUInt(&isOk,kBaseHex); + + OstProto::Vlan *vlan = stream->mutable_protocol( + stream->protocol_size()-1)->MutableExtension(OstProto::vlan); + + vlan->set_vlan_tag(tag); + } + else if (name == "eth.type") + { + bool isOk; + + uint type = attributes.value("value").toString() + .toUInt(&isOk, kBaseHex); + + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kEth2FieldNumber); + + OstProto::Eth2 *eth2 = proto->MutableExtension(OstProto::eth2); + + eth2->set_type(type); + eth2->set_is_override_type(true); + } + else if (name == "eth.len") + { + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kDot3FieldNumber); + + OstProto::Dot3 *dot3 = proto->MutableExtension(OstProto::dot3); + + bool isOk; + dot3->set_length(attributes.value("value").toString().toUInt(&isOk, kBaseHex)); + dot3->set_is_override_length(true); + } +#if 0 + else if (name == "eth.trailer") + { + QByteArray trailer = QByteArray::fromHex( + attributes.value("value").toString().toUtf8()); + + stream->mutable_core()->mutable_name()->append(trailer.constData(), + trailer.size()); + } + else if ((name == "eth.fcs") || + attributes.value("show").toString().startsWith("Frame check sequence")) + { + QByteArray trailer = QByteArray::fromHex( + attributes.value("value").toString().toUtf8()); + + stream->mutable_core()->mutable_name()->append(trailer.constData(), + trailer.size()); + } +#endif +} + diff --git a/common/eth2pdml.h b/common/eth2pdml.h new file mode 100644 index 0000000..b776147 --- /dev/null +++ b/common/eth2pdml.h @@ -0,0 +1,38 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ETH2_PDML_H +#define _ETH2_PDML_H + +#include "pdmlprotocol.h" + +class PdmlEthProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + +protected: + PdmlEthProtocol(); +}; + +#endif diff --git a/common/fileformat.cpp b/common/fileformat.cpp new file mode 100644 index 0000000..4edd980 --- /dev/null +++ b/common/fileformat.cpp @@ -0,0 +1,483 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "fileformat.h" + +#include "crc32c.h" + +#include +#include +#include + +#include + +const std::string FileFormat::kFileMagicValue = "\xa7\xb7OSTINATO"; + +FileFormat fileFormat; + +const int kBaseHex = 16; + +FileFormat::FileFormat() +{ + /* + * We don't have any "real" work to do here in the constructor. + * What we do is run some "assert" tests so that these get caught + * at init itself instead of while saving/restoring when a user + * might lose some data! + */ + OstProto::FileMagic magic; + OstProto::FileChecksum cksum; + + magic.set_value(kFileMagicValue); + cksum.set_value(quint32(0)); + + // TODO: convert Q_ASSERT to something that will run in RELEASE mode also + Q_ASSERT(magic.IsInitialized()); + Q_ASSERT(cksum.IsInitialized()); + Q_ASSERT(magic.ByteSize() == kFileMagicSize); + Q_ASSERT(cksum.ByteSize() == kFileChecksumSize); +} + +FileFormat::~FileFormat() +{ +} + +bool FileFormat::openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error) +{ + QFile file(fileName); + QByteArray buf; + int size, contentOffset, contentSize; + quint32 calcCksum; + OstProto::FileMagic magic; + OstProto::FileMeta meta; + OstProto::FileContent content; + OstProto::FileChecksum cksum, zeroCksum; + + if (!file.open(QIODevice::ReadOnly)) + goto _open_fail; + + if (file.size() < kFileMagicSize) + goto _magic_missing; + + if (file.size() < kFileMinSize) + goto _checksum_missing; + + buf.resize(file.size()); + size = file.read(buf.data(), buf.size()); + if (size < 0) + goto _read_fail; + + Q_ASSERT(file.atEnd()); + file.close(); + + qDebug("%s: file.size() = %lld", __FUNCTION__, file.size()); + qDebug("%s: size = %d", __FUNCTION__, size); + + //qDebug("Read %d bytes", buf.size()); + //qDebug("%s", QString(buf.toHex()).toAscii().constData()); + + // Parse and verify magic + if (!magic.ParseFromArray( + (void*)(buf.constData() + kFileMagicOffset), + kFileMagicSize)) + { + goto _magic_parse_fail; + } + if (magic.value() != kFileMagicValue) + goto _magic_match_fail; + + // Parse and verify checksum + if (!cksum.ParseFromArray( + (void*)(buf.constData() + size - kFileChecksumSize), + kFileChecksumSize)) + { + goto _cksum_parse_fail; + } + + zeroCksum.set_value(0); + if (!zeroCksum.SerializeToArray( + (void*) (buf.data() + size - kFileChecksumSize), + kFileChecksumSize)) + { + goto _zero_cksum_serialize_fail; + } + + calcCksum = checksumCrc32C((quint8*) buf.constData(), size); + + qDebug("checksum \nExpected:%x Actual:%x", + calcCksum, cksum.value()); + + if (cksum.value() != calcCksum) + goto _cksum_verify_fail; + + // Parse the metadata first before we parse the full contents + if (!meta.ParseFromArray( + (void*)(buf.constData() + kFileMetaDataOffset), + size - kFileMetaDataOffset)) + { + goto _metadata_parse_fail; + } + + qDebug("%s: File MetaData (INFORMATION) - \n%s", __FUNCTION__, + QString().fromStdString(meta.DebugString()).toAscii().constData()); + + // MetaData Validation(s) + if (meta.data().file_type() != OstProto::kStreamsFileType) + goto _unexpected_file_type; + + if (meta.data().format_version_major() != kFileFormatVersionMajor) + goto _incompatible_file_version; + + if (meta.data().format_version_minor() > kFileFormatVersionMinor) + goto _incompatible_file_version; + + if (meta.data().format_version_minor() < kFileFormatVersionMinor) + { + // TODO: need to modify 'buf' such that we can parse successfully + // assuming the native minor version + } + + if (meta.data().format_version_revision() > kFileFormatVersionRevision) + { + error = QString(tr("%1 was created using a newer version of Ostinato." + " New features/protocols will not be available.")).arg(fileName); + } + + Q_ASSERT(meta.data().format_version_major() == kFileFormatVersionMajor); + + // ByteSize() does not include the Tag/Key, so we add 2 for that + contentOffset = kFileMetaDataOffset + meta.data().ByteSize() + 2; + contentSize = size - contentOffset - kFileChecksumSize; + + // Parse full contents + if (!content.ParseFromArray( + (void*)(buf.constData() + contentOffset), + contentSize)) + { + goto _content_parse_fail; + } + + if (!content.matter().has_streams()) + goto _missing_streams; + + postParseFixup(meta.data(), content); + + streams.CopyFrom(content.matter().streams()); + + return true; + +_missing_streams: + error = QString(tr("%1 does not contain any streams")).arg(fileName); + goto _fail; +_content_parse_fail: + error = QString(tr("Failed parsing %1 contents")).arg(fileName); + qDebug("Error: %s", QString().fromStdString( + content.matter().InitializationErrorString()) + .toAscii().constData()); + qDebug("Debug: %s", QString().fromStdString( + content.matter().DebugString()).toAscii().constData()); + goto _fail; +_incompatible_file_version: + error = QString(tr("%1 is in an incompatible format version - %2.%3.%4" + " (Native version is %5.%6.%7)")) + .arg(fileName) + .arg(meta.data().format_version_major()) + .arg(meta.data().format_version_minor()) + .arg(meta.data().format_version_revision()) + .arg(kFileFormatVersionMajor) + .arg(kFileFormatVersionMinor) + .arg(kFileFormatVersionRevision); + goto _fail; +_unexpected_file_type: + error = QString(tr("%1 is not a streams file")).arg(fileName); + goto _fail; +_metadata_parse_fail: + error = QString(tr("Failed parsing %1 meta data")).arg(fileName); + qDebug("Error: %s", QString().fromStdString( + meta.data().InitializationErrorString()) + .toAscii().constData()); + goto _fail; +_cksum_verify_fail: + error = QString(tr("%1 checksum validation failed!\nExpected:%2 Actual:%3")) + .arg(fileName) + .arg(calcCksum, 0, kBaseHex) + .arg(cksum.value(), 0, kBaseHex); + goto _fail; +_zero_cksum_serialize_fail: + error = QString(tr("Internal Error: Zero Checksum Serialize failed!\n" + "Error: %1\nDebug: %2")) + .arg(QString().fromStdString( + cksum.InitializationErrorString())) + .arg(QString().fromStdString(cksum.DebugString())); + goto _fail; +_cksum_parse_fail: + error = QString(tr("Failed parsing %1 checksum")).arg(fileName); + qDebug("Error: %s", QString().fromStdString( + cksum.InitializationErrorString()) + .toAscii().constData()); + goto _fail; +_magic_match_fail: + error = QString(tr("%1 is not an Ostinato file")).arg(fileName); + goto _fail; +_magic_parse_fail: + error = QString(tr("%1 does not look like an Ostinato file")).arg(fileName); + qDebug("Error: %s", QString().fromStdString( + magic.InitializationErrorString()) + .toAscii().constData()); + goto _fail; +_read_fail: + error = QString(tr("Error reading from %1")).arg(fileName); + goto _fail; +_checksum_missing: + error = QString(tr("%1 is too small (missing checksum)")).arg(fileName); + goto _fail; +_magic_missing: + error = QString(tr("%1 is too small (missing magic value)")) + .arg(fileName); + goto _fail; +_open_fail: + error = QString(tr("Error opening %1")).arg(fileName); + goto _fail; +_fail: + qDebug("%s", error.toAscii().constData()); + return false; +} + +bool FileFormat::saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error) +{ + OstProto::FileMagic magic; + OstProto::FileMeta meta; + OstProto::FileContent content; + OstProto::FileChecksum cksum; + QFile file(fileName); + int metaSize, contentSize; + int contentOffset, cksumOffset; + QByteArray buf; + quint32 calcCksum; + + magic.set_value(kFileMagicValue); + Q_ASSERT(magic.IsInitialized()); + + cksum.set_value(0); + Q_ASSERT(cksum.IsInitialized()); + + initFileMetaData(*(meta.mutable_data())); + meta.mutable_data()->set_file_type(OstProto::kStreamsFileType); + Q_ASSERT(meta.IsInitialized()); + + if (!streams.IsInitialized()) + goto _stream_not_init; + + content.mutable_matter()->mutable_streams()->CopyFrom(streams); + Q_ASSERT(content.IsInitialized()); + + metaSize = meta.ByteSize(); + contentSize = content.ByteSize(); + contentOffset = kFileMetaDataOffset + metaSize; + cksumOffset = contentOffset + contentSize; + + Q_ASSERT(magic.ByteSize() == kFileMagicSize); + Q_ASSERT(cksum.ByteSize() == kFileChecksumSize); + buf.resize(kFileMagicSize + metaSize + contentSize + kFileChecksumSize); + + // Serialize everything + if (!magic.SerializeToArray((void*) (buf.data() + kFileMagicOffset), + kFileMagicSize)) + { + goto _magic_serialize_fail; + } + + if (!meta.SerializeToArray((void*) (buf.data() + kFileMetaDataOffset), + metaSize)) + { + goto _meta_serialize_fail; + } + + if (!content.SerializeToArray((void*) (buf.data() + contentOffset), + contentSize)) + { + goto _content_serialize_fail; + } + + if (!cksum.SerializeToArray((void*) (buf.data() + cksumOffset), + kFileChecksumSize)) + { + goto _zero_cksum_serialize_fail; + } + + emit status("Calculating checksum..."); + + // Calculate and write checksum + calcCksum = checksumCrc32C((quint8*)buf.constData(), buf.size()); + cksum.set_value(calcCksum); + if (!cksum.SerializeToArray( + (void*) (buf.data() + cksumOffset), + kFileChecksumSize)) + { + goto _cksum_serialize_fail; + } + + qDebug("Writing %d bytes", buf.size()); + //qDebug("%s", QString(buf.toHex()).toAscii().constData()); + + emit status("Writing to disk..."); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + goto _open_fail; + + if (file.write(buf) < 0) + goto _write_fail; + + file.close(); + + return true; + +_write_fail: + error = QString(tr("Error writing to %1")).arg(fileName); + goto _fail; +_open_fail: + error = QString(tr("Error opening %1 (Error Code = %2)")) + .arg(fileName) + .arg(file.error()); + goto _fail; +_cksum_serialize_fail: + error = QString(tr("Internal Error: Checksum Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + cksum.InitializationErrorString())) + .arg(QString().fromStdString(cksum.DebugString())); + goto _fail; +_zero_cksum_serialize_fail: + error = QString(tr("Internal Eror: Zero Checksum Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + cksum.InitializationErrorString())) + .arg(QString().fromStdString(cksum.DebugString())); + goto _fail; +_content_serialize_fail: + error = QString(tr("Internal Error: Content Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + content.InitializationErrorString())) + .arg(QString().fromStdString(content.DebugString())); + goto _fail; +_meta_serialize_fail: + error = QString(tr("Internal Error: Meta Data Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + meta.InitializationErrorString())) + .arg(QString().fromStdString(meta.DebugString())); + goto _fail; +_magic_serialize_fail: + error = QString(tr("Internal Error: Magic Serialize failed\n%1\n%2")) + .arg(QString().fromStdString( + magic.InitializationErrorString())) + .arg(QString().fromStdString(magic.DebugString())); + goto _fail; +_stream_not_init: + error = QString(tr("Internal Error: Streams not initialized\n%1\n%2")) + .arg(QString().fromStdString( + streams.InitializationErrorString())) + .arg(QString().fromStdString(streams.DebugString())); + goto _fail; +_fail: + qDebug("%s", error.toAscii().constData()); + return false; +} + +bool FileFormat::isMyFileFormat(const QString fileName) +{ + bool ret = false; + QFile file(fileName); + QByteArray buf; + OstProto::FileMagic magic; + + if (!file.open(QIODevice::ReadOnly)) + goto _exit; + + buf = file.peek(kFileMagicOffset + kFileMagicSize); + if (!magic.ParseFromArray((void*)(buf.constData() + kFileMagicOffset), + kFileMagicSize)) + goto _close_exit; + + if (magic.value() == kFileMagicValue) + ret = true; + +_close_exit: + file.close(); +_exit: + return ret; +} + +bool FileFormat::isMyFileType(const QString fileType) +{ + if (fileType.startsWith("Ostinato")) + return true; + else + return false; +} + +void FileFormat::initFileMetaData(OstProto::FileMetaData &metaData) +{ + // Fill in the "native" file format version + metaData.set_format_version_major(kFileFormatVersionMajor); + metaData.set_format_version_minor(kFileFormatVersionMinor); + metaData.set_format_version_revision(kFileFormatVersionRevision); + + metaData.set_generator_name( + qApp->applicationName().toUtf8().constData()); + metaData.set_generator_version( + qApp->property("version").toString().toUtf8().constData()); + metaData.set_generator_revision( + qApp->property("revision").toString().toUtf8().constData()); +} + +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +/*! Fixup content to what is expected in the native version */ +void FileFormat::postParseFixup(OstProto::FileMetaData metaData, + OstProto::FileContent &content) +{ + Q_ASSERT(metaData.format_version_major() == kFileFormatVersionMajor); + + // Do fixups from oldest to newest versions + switch (metaData.format_version_minor()) + { + case 1: + { + int n = content.matter().streams().stream_size(); + for (int i = 0; i < n; i++) + { + OstProto::StreamControl *sctl = + content.mutable_matter()->mutable_streams()->mutable_stream(i)->mutable_control(); + sctl->set_packets_per_sec(sctl->obsolete_packets_per_sec()); + sctl->set_bursts_per_sec(sctl->obsolete_bursts_per_sec()); + } + + // fall-through to next higher version until native version + } + case kFileFormatVersionMinor: // native version + break; + + case 0: + default: + qWarning("%s: minor version %u unhandled", __FUNCTION__, + metaData.format_version_minor()); + Q_ASSERT_X(false, "postParseFixup", "unhandled minor version"); + } + +} +#pragma GCC diagnostic warning "-Wdeprecated-declarations" + diff --git a/common/fileformat.h b/common/fileformat.h new file mode 100644 index 0000000..409b8a8 --- /dev/null +++ b/common/fileformat.h @@ -0,0 +1,62 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _FILE_FORMAT_H +#define _FILE_FORMAT_H + +#include "abstractfileformat.h" + +#include "fileformat.pb.h" + +class FileFormat : public AbstractFileFormat +{ +public: + FileFormat(); + ~FileFormat(); + + virtual bool openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error); + virtual bool saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error); + + bool isMyFileFormat(const QString fileName); + bool isMyFileType(const QString fileType); + +private: + void initFileMetaData(OstProto::FileMetaData &metaData); + void postParseFixup(OstProto::FileMetaData metaData, + OstProto::FileContent &content); + + static const int kFileMagicSize = 12; + static const int kFileChecksumSize = 5; + static const int kFileMinSize = kFileMagicSize + kFileChecksumSize; + + static const int kFileMagicOffset = 0; + static const int kFileMetaDataOffset = kFileMagicSize; + + static const std::string kFileMagicValue; + + // Native file format version + static const uint kFileFormatVersionMajor = 0; + static const uint kFileFormatVersionMinor = 2; + static const uint kFileFormatVersionRevision = 3; +}; + +extern FileFormat fileFormat; + +#endif diff --git a/common/fileformat.proto b/common/fileformat.proto new file mode 100644 index 0000000..ce2a688 --- /dev/null +++ b/common/fileformat.proto @@ -0,0 +1,98 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +enum FileType { + kReservedFileType = 0; + kStreamsFileType = 1; +} + +message FileMetaData { + required FileType file_type = 1; + required uint32 format_version_major = 2; + required uint32 format_version_minor = 3; + required uint32 format_version_revision = 4; + required string generator_name = 5; + required string generator_version = 6; + required string generator_revision = 7; +} + +message FileContentMatter { + optional StreamConfigList streams = 1; +} + +/* + An Ostinato file is the binary encoding of the File message below + STRICTLY in increasing order of field number for the top level fields + + We do not use field number '1' for magic value because its encoded key + is '0a' (LF or \n) which occurs commonly in text files. Checksum should + be the last field, so top level field numbers greater than 15 are not + permitted. We use 15 as the checksum field number because it is the + largest field number that can fit in a 1-byte tag + + The magic value is of a fixed length so that meta data has a fixed offset + from the start in the encoded message. + + Checksum is fixed length so that it is at a fixed negative offset from + the end in the encoded message. + + Because the protobuf serialization API does not _guarantee_ + strict ordering, so we define wrapper messages for each top level field + and serialize the individual wrapper messages. The field numbers MUST + be the same in 'File' and the wrapper messages +*/ +message File { + // FieldNumber 1 is reserved and MUST not be used! + required bytes magic_value = 2; + required FileMetaData meta_data = 3; + optional FileContent content_matter = 9; + required fixed32 checksum_value = 15; +} + +/* + The magic value is 10 bytes - "\xa7\xb7OSTINATO" + The 1st-2nd byte has the MSB set to avoid mixup with text files + The 3rd-10th byte spell OSTINATO (duh!) + + Encoded Size : Key(1) + Length(1) + Value(10) = 12 bytes + Encoded Value: 120aa7b7 4f535449 4e41544f +*/ +message FileMagic { + required bytes value = 2; +} + +message FileMeta { + required FileMetaData data = 3; +} + +message FileContent { + optional FileContentMatter matter = 9; +} + +/* + Encoded Size : Key(1) + Value(4) = 5 bytes + Encoded Value: 7d xxXXxxXX +*/ +message FileChecksum { + required fixed32 value = 15; // should always be a fixed 32-bit size +} diff --git a/common/gmp.cpp b/common/gmp.cpp new file mode 100755 index 0000000..240650d --- /dev/null +++ b/common/gmp.cpp @@ -0,0 +1,769 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "gmp.h" +#include + +QHash GmpProtocol::frameFieldCountMap; + +GmpProtocol::GmpProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +GmpProtocol::~GmpProtocol() +{ +} + +AbstractProtocol::ProtocolIdType GmpProtocol::protocolIdType() const +{ + return ProtocolIdIp; +} + +int GmpProtocol::fieldCount() const +{ + return FIELD_COUNT; +} + +int GmpProtocol::frameFieldCount() const +{ + int type = msgType(); + + // frameFieldCountMap contains the frameFieldCounts for each + // msgType - this is built on demand and cached for subsequent use + + // lookup if we have already cached ... + if (frameFieldCountMap.contains(type)) + return frameFieldCountMap.value(type); + + // ... otherwise calculate and cache + int count = 0; + for (int i = 0; i < FIELD_COUNT; i++) + { + if (fieldFlags(i).testFlag(AbstractProtocol::FrameField)) + count++; + } + frameFieldCountMap.insert(type, count); + return count; +} + +AbstractProtocol::FieldFlags GmpProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + flags &= ~FrameField; + + switch(index) + { + // Frame Fields - check against msgType() + case kType: + case kRsvdMrtCode: + flags |= FrameField; + break; + case kChecksum: + flags |= FrameField; + flags |= CksumField; + break; + case kMldMrt: + case kMldRsvd: + // MLD subclass should handle suitably + break; + + case kGroupAddress: + if (!isSsmReport()) + flags |= FrameField; + break; + + case kRsvd1: + case kSFlag: + case kQrv: + case kQqic: + case kSourceCount: + case kSources: + if (isSsmQuery()) + flags |= FrameField; + break; + + case kRsvd2: + case kGroupRecordCount: + case kGroupRecords: + if (isSsmReport()) + flags |= FrameField; + break; + + // Meta Fields + case kIsOverrideChecksum: + case kGroupMode: + case kGroupCount: + case kGroupPrefix: + case kIsOverrideSourceCount: + case kIsOverrideGroupRecordCount: + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +QVariant GmpProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case kType: + { + uint type = data.type(); + + switch(attrib) + { + case FieldName: + return QString("Type"); + case FieldValue: + return type; + case FieldTextValue: + return QString("%1").arg(quint8(type)); + case FieldFrameValue: + return QByteArray(1, quint8(type)); + default: + break; + } + break; + } + case kRsvdMrtCode: + { + quint8 rsvd = 0; + + switch(attrib) + { + case FieldName: + return QString("Reserved"); + case FieldValue: + return rsvd; + case FieldTextValue: + return QString("%1").arg(rsvd); + case FieldFrameValue: + return QByteArray(1, rsvd); + default: + break; + } + break; + } + case kChecksum: + { + switch(attrib) + { + case FieldName: + return QString("Checksum"); + case FieldBitSize: + return 16; + default: + break; + } + + quint16 cksum = data.is_override_checksum() ? + data.checksum() : checksum(streamIndex); + + switch(attrib) + { + case FieldValue: + return cksum; + case FieldFrameValue: + { + QByteArray fv; + + fv.resize(2); + qToBigEndian(cksum, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QString("0x%1").arg(cksum, 4, BASE_HEX, QChar('0')); + default: + break; + } + break; + } + case kMldMrt: + case kMldRsvd: + // XXX: Present only in MLD - hence handled by the mld subclass + break; + + case kGroupAddress: + // XXX: Handled by each subclass + break; + + case kRsvd1: + { + quint8 rsvd = 0; + + switch(attrib) + { + case FieldName: + return QString("Reserved"); + case FieldValue: + return rsvd; + case FieldTextValue: + return QString("%1").arg(rsvd); + case FieldFrameValue: + return QByteArray(1, char(rsvd)); + case FieldBitSize: + return 4; + default: + break; + } + break; + } + case kSFlag: + { + switch(attrib) + { + case FieldName: + return QString("S Flag"); + case FieldValue: + return data.s_flag(); + case FieldTextValue: + return data.s_flag() ? QString("True") : QString("False"); + case FieldFrameValue: + return QByteArray(1, char(data.s_flag())); + case FieldBitSize: + return 1; + default: + break; + } + break; + } + case kQrv: + { + int qrv = data.qrv() & 0x7; + + switch(attrib) + { + case FieldName: + return QString("QRV"); + case FieldValue: + return qrv; + case FieldTextValue: + return QString("%1").arg(qrv); + case FieldFrameValue: + return QByteArray(1, char(qrv)); + case FieldBitSize: + return 3; + default: + break; + } + break; + } + case kQqic: + { + int qqi = data.qqi(); + + switch(attrib) + { + case FieldName: + return QString("QQIC"); + case FieldValue: + return qqi; + case FieldTextValue: + return QString("%1").arg(qqi); + case FieldFrameValue: + { + char qqicode = char(qqic(qqi)); + return QByteArray(1, qqicode); + } + default: + break; + } + break; + } + case kSourceCount: + { + quint16 count = data.sources_size(); + + if (data.is_override_source_count()) + count = data.source_count(); + + switch(attrib) + { + case FieldName: + return QString("Number of Sources"); + case FieldValue: + return count; + case FieldTextValue: + return QString("%1").arg(count); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(count, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case kSources: + // XXX: Handled by each subclass + break; + case kRsvd2: + { + quint16 rsvd = 0; + + switch(attrib) + { + case FieldName: + return QString("Reserved"); + case FieldValue: + return rsvd; + case FieldTextValue: + return QString("%1").arg(rsvd); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(rsvd, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case kGroupRecordCount: + { + quint16 count = data.group_records_size(); + + if (data.is_override_group_record_count()) + count = data.group_record_count(); + + switch(attrib) + { + case FieldName: + return QString("Number of Group Records"); + case FieldValue: + return count; + case FieldTextValue: + return QString("%1").arg(count); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(count, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case kGroupRecords: + { + switch(attrib) + { + case FieldName: + return QString("Group List"); + case FieldValue: + { + QVariantList grpRecords; + + for (int i = 0; i < data.group_records_size(); i++) + { + QVariantMap grpRec; + OstProto::Gmp::GroupRecord rec = data.group_records(i); + + grpRec["groupRecordType"] = rec.type(); + // grpRec["groupRecordAddress"] = subclass responsibility + grpRec["overrideGroupRecordSourceCount"] = + rec.is_override_source_count(); + grpRec["groupRecordSourceCount"] = rec.source_count(); + + // grpRec["groupRecordSourceList"] = subclass responsibility + grpRec["overrideAuxDataLength"] = + rec.is_override_aux_data_length(); + grpRec["auxDataLength"] = rec.aux_data_length(); + grpRec["auxData"] = QByteArray().append( + QString::fromStdString(rec.aux_data())); + + grpRecords.append(grpRec); + } + return grpRecords; + } + case FieldFrameValue: + { + QVariantList fv; + for (int i = 0; i < data.group_records_size(); i++) + { + OstProto::Gmp::GroupRecord rec = data.group_records(i); + QByteArray rv; + quint16 srcCount; + + rv.resize(4); + rv[0] = rec.type(); + rv[1] = rec.is_override_aux_data_length() ? + rec.aux_data_length() : rec.aux_data().size()/4; + + if (rec.is_override_source_count()) + srcCount = rec.source_count(); + else + srcCount = rec.sources_size(); + qToBigEndian(srcCount, (uchar*)(rv.data()+2)); + + // group_address => subclass responsibility + // source list => subclass responsibility + + rv.append(QString().fromStdString(rec.aux_data())); + + fv.append(rv); + } + return fv; + } + case FieldTextValue: + { + QStringList list; + + for (int i = 0; i < data.group_records_size(); i++) + { + OstProto::Gmp::GroupRecord rec = data.group_records(i); + QString str; + + str.append(" Type: "); + switch(rec.type()) + { + case OstProto::Gmp::GroupRecord::kIsInclude: + str.append("IS_INCLUDE"); break; + case OstProto::Gmp::GroupRecord::kIsExclude: + str.append("IS_EXCLUDE"); break; + case OstProto::Gmp::GroupRecord::kToInclude: + str.append("TO_INCLUDE"); break; + case OstProto::Gmp::GroupRecord::kToExclude: + str.append("TO_EXCLUDE"); break; + case OstProto::Gmp::GroupRecord::kAllowNew: + str.append("ALLOW_NEW"); break; + case OstProto::Gmp::GroupRecord::kBlockOld: + str.append("BLOCK_OLD"); break; + default: + str.append("UNKNOWN"); break; + } + str.append(QString("; AuxLen: %1").arg( + rec.is_override_aux_data_length() ? + rec.aux_data_length() : rec.aux_data().size()/4)); + str.append(QString("; Source Count: %1").arg( + rec.is_override_source_count() ? + rec.source_count(): rec.sources_size())); + + // NOTE: subclass should replace the XXX below with + // group address and source list + str.append(QString("; XXX")); + + str.append(QString("; AuxData: ").append( + QByteArray().append(QString().fromStdString( + rec.aux_data())).toHex())); + + list.append(str); + } + return list; + } + default: + break; + } + break; + } + + // Meta Fields + case kIsOverrideChecksum: + { + switch(attrib) + { + case FieldValue: return data.is_override_checksum(); + default: break; + } + break; + } + case kGroupMode: + { + switch(attrib) + { + case FieldValue: return data.group_mode(); + default: break; + } + break; + } + case kGroupCount: + { + switch(attrib) + { + case FieldValue: return data.group_count(); + default: break; + } + break; + } + case kGroupPrefix: + { + switch(attrib) + { + case FieldValue: return data.group_prefix(); + default: break; + } + break; + } + case kIsOverrideSourceCount: + { + switch(attrib) + { + case FieldValue: return data.is_override_source_count(); + default: break; + } + break; + } + case kIsOverrideGroupRecordCount: + { + switch(attrib) + { + case FieldValue: return data.is_override_group_record_count(); + default: break; + } + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool GmpProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case kType: + { + uint type = value.toUInt(&isOk); + if (isOk) + data.set_type(type); + break; + } + case kRsvdMrtCode: + { + uint val = value.toUInt(&isOk); + if (isOk) + data.set_rsvd_code(val); + break; + } + case kChecksum: + { + uint csum = value.toUInt(&isOk); + if (isOk) + data.set_checksum(csum); + break; + } + case kMldMrt: + { + uint mrt = value.toUInt(&isOk); + if (isOk) + data.set_max_response_time(mrt); + break; + } + case kGroupAddress: + // XXX: Handled by subclass + isOk = false; + break; + case kRsvd1: + isOk = false; + break; + case kSFlag: + { + bool flag = value.toBool(); + data.set_s_flag(flag); + isOk = true; + break; + } + case kQrv: + { + uint qrv = value.toUInt(&isOk); + if (isOk) + data.set_qrv(qrv); + break; + } + case kQqic: + { + uint qqi = value.toUInt(&isOk); + if (isOk) + data.set_qqi(qqi); + break; + } + case kSourceCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_source_count(count); + break; + } + case kSources: + // XXX: Handled by subclass + isOk = false; + break; + case kRsvd2: + isOk = false; + break; + case kGroupRecordCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_group_record_count(count); + break; + } + case kGroupRecords: + { + QVariantList list = value.toList(); + + data.clear_group_records(); + + for (int i = 0; i < list.count(); i++) + { + QVariantMap grpRec = list.at(i).toMap(); + OstProto::Gmp::GroupRecord *rec = data.add_group_records(); + + rec->set_type(OstProto::Gmp::GroupRecord::RecordType( + grpRec["groupRecordType"].toInt())); + // NOTE: rec->group_address => subclass responsibility + rec->set_is_override_source_count( + grpRec["overrideGroupRecordSourceCount"].toBool()); + rec->set_source_count(grpRec["groupRecordSourceCount"].toUInt()); + // NOTE: rec->sources => subclass responsibility + rec->set_is_override_aux_data_length( + grpRec["overrideAuxDataLength"].toBool()); + rec->set_aux_data_length(grpRec["auxDataLength"].toUInt()); + QByteArray ba = grpRec["auxData"].toByteArray(); + // pad to word boundary + if (ba.size() % 4) + ba.append(QByteArray(4 - (ba.size() % 4), char(0))); + rec->set_aux_data(std::string(ba.constData(), ba.size())); + } + + break; + } + + // Meta Fields + case kIsOverrideChecksum: + { + bool ovr = value.toBool(); + data.set_is_override_checksum(ovr); + isOk = true; + break; + } + + case kGroupMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.GroupMode_IsValid(mode)) + data.set_group_mode((OstProto::Gmp::GroupMode)mode); + break; + } + case kGroupCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_group_count(count); + break; + } + case kGroupPrefix: + { + uint prefix = value.toUInt(&isOk); + if (isOk) + data.set_group_prefix(prefix); + break; + } + + case kIsOverrideSourceCount: + { + bool ovr = value.toBool(); + data.set_is_override_source_count(ovr); + isOk = true; + break; + } + + case kIsOverrideGroupRecordCount: + { + bool ovr = value.toBool(); + data.set_is_override_group_record_count(ovr); + isOk = true; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +int GmpProtocol::protocolFrameSize(int streamIndex) const +{ + // TODO: Calculate to reduce processing cost + return AbstractProtocol::protocolFrameValue(streamIndex, true).size(); +} + +bool GmpProtocol::isProtocolFrameValueVariable() const +{ + // No fields vary for Ssm Report + if (isSsmReport()) + return false; + + // For all other msg types, check the group mode + if (fieldData(kGroupMode, FieldValue).toUInt() + != uint(OstProto::Gmp::kFixed)) + return true; + + return false; +} + +int GmpProtocol::protocolFrameVariableCount() const +{ + int count = 1; + + // No fields vary for Ssm Report + if (isSsmReport()) + return count; + + // For all other msg types, check the group mode + if (fieldData(kGroupMode, FieldValue).toUInt() + != uint(OstProto::Gmp::kFixed)) + { + count = AbstractProtocol::lcm(count, + fieldData(kGroupCount, FieldValue).toUInt()); + } + + return count; +} diff --git a/common/gmp.h b/common/gmp.h new file mode 100755 index 0000000..a293c1f --- /dev/null +++ b/common/gmp.h @@ -0,0 +1,129 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _GMP_H +#define _GMP_H + +#include "abstractprotocol.h" +#include "gmp.pb.h" + +#include + +/* +Gmp Protocol Frame Format - TODO: for now see the respective RFCs +*/ + +class GmpProtocol : public AbstractProtocol +{ +public: + enum GmpField + { + // ------------ + // Frame Fields + // ------------ + // Fields used in all ASM and SSM messages, unless otherwise specified + kType = 0, + kRsvdMrtCode, + kChecksum, + kMldMrt, // MLD Only (except MLDv2 Report) + kMldRsvd, // MLD Only (except MLDv2 Report) + + // Field used in ASM messages + kGroupAddress, + FIELD_COUNT_ASM_ALL, + + // Fields used in SSM Query + kRsvd1 = FIELD_COUNT_ASM_ALL, + kSFlag, + kQrv, + kQqic, + kSourceCount, + kSources, + FIELD_COUNT_SSM_QUERY, + + // Fields used in SSM Report + kRsvd2 = FIELD_COUNT_SSM_QUERY, + kGroupRecordCount, + kGroupRecords, + FIELD_COUNT_SSM_REPORT, + FRAME_FIELD_COUNT = FIELD_COUNT_SSM_REPORT, + + // ----------- + // Meta Fields + // ----------- + kIsOverrideChecksum = FRAME_FIELD_COUNT, + + kGroupMode, + kGroupCount, + kGroupPrefix, + + kIsOverrideSourceCount, + + kIsOverrideGroupRecordCount, + + FIELD_COUNT + }; + + GmpProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~GmpProtocol(); + + virtual ProtocolIdType protocolIdType() const; + + virtual int fieldCount() const; + virtual int frameFieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual int protocolFrameSize(int streamIndex = 0) const; + + virtual bool isProtocolFrameValueVariable() const; + virtual int protocolFrameVariableCount() const; + +protected: + OstProto::Gmp data; + + int msgType() const; + + virtual bool isSsmReport() const = 0; + virtual bool isQuery() const = 0; + virtual bool isSsmQuery() const = 0; + + int qqic(int value) const; + + virtual quint16 checksum(int streamIndex) const = 0; + +private: + static QHash frameFieldCountMap; +}; + +inline int GmpProtocol::msgType() const +{ + return fieldData(kType, FieldValue).toInt(); +} + +inline int GmpProtocol::qqic(int value) const +{ + return quint8(value); // TODO: if value > 128 convert to mantissa/exp form +} + +#endif diff --git a/common/gmp.proto b/common/gmp.proto new file mode 100755 index 0000000..f1fbf56 --- /dev/null +++ b/common/gmp.proto @@ -0,0 +1,94 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Group Management Protocol (i.e. IGMP and MLD) +message Gmp { + // + // Common fields for both ASM and SSM messages + // + optional uint32 type = 1; + optional bool is_override_rsvd_code = 2; + optional uint32 rsvd_code = 3; + // MaxRespTime is in milliseconds - MaxRespCode will be derived + optional uint32 max_response_time = 4 [default = 100]; + optional bool is_override_checksum = 5; + optional uint32 checksum = 6; + + message IpAddress { + optional fixed32 v4 = 1; + optional fixed64 v6_hi = 2; + optional fixed64 v6_lo = 3; + } + + // + // Fields used in ASM messages + // + enum GroupMode { + kFixed = 0; + kIncrementGroup = 1; + kDecrementGroup = 2; + kRandomGroup = 3; + } + optional IpAddress group_address = 10; + optional GroupMode group_mode = 11 [default = kFixed]; + optional uint32 group_count = 12 [default = 16]; + optional uint32 group_prefix = 13 [default = 24]; + + // + // Fields used in SSM Query + // + optional bool s_flag = 20; + optional uint32 qrv = 21 [default = 2]; + // QuerierQueryInterval is in seconds - QQIC will be derived + optional uint32 qqi = 22 [default = 125]; + repeated IpAddress sources = 23; + optional bool is_override_source_count = 24; + optional uint32 source_count = 25; + + // + // Fields used in SSM Reports + // + message GroupRecord { + enum RecordType { + kReserved = 0; + kIsInclude = 1; + kIsExclude = 2; + kToInclude = 3; + kToExclude = 4; + kAllowNew = 5; + kBlockOld = 6; + } + + optional RecordType type = 1 [default = kIsInclude]; + optional IpAddress group_address = 2; + repeated IpAddress sources = 3; + optional bool is_override_source_count = 4; + optional uint32 source_count = 5; + optional bytes aux_data = 6; + optional bool is_override_aux_data_length = 7; + optional uint32 aux_data_length = 8; + } + repeated GroupRecord group_records = 30; + optional bool is_override_group_record_count = 31; + optional uint32 group_record_count = 32; +} diff --git a/common/gmp.ui b/common/gmp.ui new file mode 100755 index 0000000..6260af6 --- /dev/null +++ b/common/gmp.ui @@ -0,0 +1,835 @@ + + Gmp + + + + 0 + 0 + 509 + 355 + + + + Form + + + + + + + + Message Type + + + msgTypeCombo + + + + + + + + + + Max Response Time (1/10s) + + + maxResponseTime + + + + + + + + 0 + 0 + + + + + + + + Checksum + + + + + + + false + + + + 0 + 0 + + + + >HHHH; + + + + + + + + + + + + + + + Group Address + + + groupAddress + + + + + + + Mode + + + msgTypeCombo + + + + + + + Count + + + msgTypeCombo + + + + + + + Prefix + + + msgTypeCombo + + + + + + + + 1 + 0 + + + + + + + + + Fixed + + + + + Increment Host + + + + + Decrement Host + + + + + Random Host + + + + + + + + false + + + + 0 + 0 + + + + + + + + false + + + + 0 + 0 + + + + /900; + + + + + + + + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + S Flag (Suppress Router Processing) + + + + + + + QRV + + + qrv + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + QQI + + + qqi + + + + + + + + + + Qt::Vertical + + + + 61 + 41 + + + + + + + + + + + + + + Source List + + + groupRecordAddress + + + + + + + Qt::Horizontal + + + + 16 + 20 + + + + + + + + + + + + + + + + – + + + + + + + + + true + + + QAbstractItemView::InternalMove + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Count + + + + + + + false + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + Group Records + + + groupRecordAddress + + + + + + + Qt::Horizontal + + + + 16 + 20 + + + + + + + + + + + + + + + + – + + + + + + + + + true + + + QAbstractItemView::InternalMove + + + + + + + + + Number of Groups + + + + + + + false + + + + + + + + + + + false + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + Record Type + + + groupRecordType + + + + + + + + Reserved + + + + + Is Include + + + + + Is Exclude + + + + + To Include + + + + + To Exclude + + + + + Allow New + + + + + Block Old + + + + + + + + Group Address + + + groupRecordAddress + + + + + + + + + + + + + + + + Source List + + + groupRecordAddress + + + + + + + Qt::Horizontal + + + + 191 + 20 + + + + + + + + + + + + + + + + – + + + + + + + + + true + + + QAbstractItemView::InternalMove + + + + + + + + + Number of Sources + + + + + + + false + + + + 0 + 0 + + + + + + + + Qt::Horizontal + + + + 81 + 20 + + + + + + + + + + + + + + Aux Data + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Length (x4) + + + + + + + false + + + + 0 + 0 + + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 101 + 21 + + + + + + + + + IntComboBox + QComboBox +
intcombobox.h
+
+
+ + msgTypeCombo + maxResponseTime + overrideChecksum + checksum + groupAddress + groupMode + groupCount + groupPrefix + overrideGroupRecordCount + groupRecordCount + groupRecordType + groupRecordAddress + overrideGroupRecordSourceCount + groupRecordSourceCount + overrideAuxDataLength + auxDataLength + auxData + sFlag + qrv + qqi + overrideSourceCount + sourceCount + + + + + overrideChecksum + toggled(bool) + checksum + setEnabled(bool) + + + 391 + 43 + + + 448 + 45 + + + + + overrideGroupRecordSourceCount + toggled(bool) + groupRecordSourceCount + setEnabled(bool) + + + 402 + 202 + + + 436 + 204 + + + + + overrideAuxDataLength + toggled(bool) + auxDataLength + setEnabled(bool) + + + 416 + 286 + + + 433 + 286 + + + + + overrideGroupRecordCount + toggled(bool) + groupRecordCount + setEnabled(bool) + + + 112 + 309 + + + 138 + 312 + + + + + overrideSourceCount + toggled(bool) + sourceCount + setEnabled(bool) + + + 413 + 154 + + + 434 + 151 + + + + +
diff --git a/common/gmpconfig.cpp b/common/gmpconfig.cpp new file mode 100644 index 0000000..ee6af11 --- /dev/null +++ b/common/gmpconfig.cpp @@ -0,0 +1,384 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "gmpconfig.h" +#include "gmp.h" + +GmpConfigForm::GmpConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); + + auxData->setValidator(new QRegExpValidator( + QRegExp("[0-9A-Fa-f]*"), this)); +} + +GmpConfigForm::~GmpConfigForm() +{ +} + +void GmpConfigForm::loadWidget(AbstractProtocol *proto) +{ + msgTypeCombo->setValue( + proto->fieldData( + GmpProtocol::kType, + AbstractProtocol::FieldValue + ).toUInt()); + + // XXX: maxResponseTime set by subclass + + overrideChecksum->setChecked( + proto->fieldData( + GmpProtocol::kIsOverrideChecksum, + AbstractProtocol::FieldValue + ).toBool()); + checksum->setText(uintToHexStr( + proto->fieldData( + GmpProtocol::kChecksum, + AbstractProtocol::FieldValue + ).toUInt(), 2)); + + groupAddress->setText( + proto->fieldData( + GmpProtocol::kGroupAddress, + AbstractProtocol::FieldValue + ).toString()); + groupMode->setCurrentIndex( + proto->fieldData( + GmpProtocol::kGroupMode, + AbstractProtocol::FieldValue + ).toUInt()); + groupCount->setText( + proto->fieldData( + GmpProtocol::kGroupCount, + AbstractProtocol::FieldValue + ).toString()); + groupPrefix->setText( + proto->fieldData( + GmpProtocol::kGroupPrefix, + AbstractProtocol::FieldValue + ).toString()); + + sFlag->setChecked( + proto->fieldData( + GmpProtocol::kSFlag, + AbstractProtocol::FieldValue + ).toBool()); + qrv->setText( + proto->fieldData( + GmpProtocol::kQrv, + AbstractProtocol::FieldValue + ).toString()); + qqi->setText( + proto->fieldData( + GmpProtocol::kQqic, + AbstractProtocol::FieldValue + ).toString()); + + QStringList sl = + proto->fieldData( + GmpProtocol::kSources, + AbstractProtocol::FieldValue + ).toStringList(); + sourceList->clear(); + foreach(QString src, sl) + { + QListWidgetItem *item = new QListWidgetItem(src); + item->setFlags(item->flags() | Qt::ItemIsEditable); + sourceList->addItem(item); + } + + // NOTE: SourceCount should be loaded after sourceList + overrideSourceCount->setChecked( + proto->fieldData( + GmpProtocol::kIsOverrideSourceCount, + AbstractProtocol::FieldValue + ).toBool()); + sourceCount->setText( + proto->fieldData( + GmpProtocol::kSourceCount, + AbstractProtocol::FieldValue + ).toString()); + + QVariantList list = + proto->fieldData( + GmpProtocol::kGroupRecords, + AbstractProtocol::FieldValue + ).toList(); + groupList->clear(); + foreach (QVariant rec, list) + { + QVariantMap grpRec = rec.toMap(); + QListWidgetItem *item = new QListWidgetItem; + + item->setData(Qt::UserRole, grpRec); + item->setText(QString("%1: %2") + .arg(groupRecordType->itemText( + grpRec["groupRecordType"].toInt())) + .arg(grpRec["groupRecordAddress"].toString())); + groupList->addItem(item); + } + + // NOTE: recordCount should be loaded after recordList + overrideGroupRecordCount->setChecked( + proto->fieldData( + GmpProtocol::kIsOverrideGroupRecordCount, + AbstractProtocol::FieldValue + ).toBool()); + groupRecordCount->setText( + proto->fieldData( + GmpProtocol::kGroupRecordCount, + AbstractProtocol::FieldValue + ).toString()); +} + +void GmpConfigForm::storeWidget(AbstractProtocol *proto) +{ + update(); + + proto->setFieldData( + GmpProtocol::kType, + msgTypeCombo->currentValue()); + + // XXX: maxResponseTime handled by subclass + + proto->setFieldData( + GmpProtocol::kIsOverrideChecksum, + overrideChecksum->isChecked()); + proto->setFieldData( + GmpProtocol::kChecksum, + hexStrToUInt(checksum->text())); + + proto->setFieldData( + GmpProtocol::kGroupAddress, + groupAddress->text()); + proto->setFieldData( + GmpProtocol::kGroupMode, + groupMode->currentIndex()); + proto->setFieldData( + GmpProtocol::kGroupCount, + groupCount->text()); + proto->setFieldData( + GmpProtocol::kGroupPrefix, + groupPrefix->text().remove('/')); + + proto->setFieldData( + GmpProtocol::kSFlag, + sFlag->isChecked()); + proto->setFieldData( + GmpProtocol::kQrv, + qrv->text()); + proto->setFieldData( + GmpProtocol::kQqic, + qqi->text()); + + QStringList list; + for (int i = 0; i < sourceList->count(); i++) + list.append(sourceList->item(i)->text()); + + proto->setFieldData( + GmpProtocol::kSources, + list); + + // sourceCount should be AFTER sources + proto->setFieldData( + GmpProtocol::kIsOverrideSourceCount, + overrideSourceCount->isChecked()); + proto->setFieldData( + GmpProtocol::kSourceCount, + sourceCount->text()); + + QVariantList grpList; + for (int i = 0; i < groupList->count(); i++) + { + QVariant grp = groupList->item(i)->data(Qt::UserRole); + grpList.append(grp.toMap()); + } + proto->setFieldData(GmpProtocol::kGroupRecords, grpList); + + // groupRecordCount should be AFTER groupRecords + proto->setFieldData( + GmpProtocol::kIsOverrideGroupRecordCount, + overrideGroupRecordCount->isChecked()); + proto->setFieldData( + GmpProtocol::kGroupRecordCount, + groupRecordCount->text()); +} + +void GmpConfigForm::update() +{ + // save the current group Record by simulating a currentItemChanged() + on_groupList_currentItemChanged(groupList->currentItem(), + groupList->currentItem()); +} + +// +// -- private slots +// + +void GmpConfigForm::on_groupMode_currentIndexChanged(int index) +{ + bool disabled = (index == 0); + + groupCount->setDisabled(disabled); + groupPrefix->setDisabled(disabled); +} + +void GmpConfigForm::on_addSource_clicked() +{ + QListWidgetItem *item=new QListWidgetItem(_defaultSourceIp); + item->setFlags(item->flags() | Qt::ItemIsEditable); + sourceList->insertItem(sourceList->currentRow(), item); + + if (!overrideSourceCount->isChecked()) + sourceCount->setText(QString().setNum(sourceList->count())); +} + +void GmpConfigForm::on_deleteSource_clicked() +{ + delete sourceList->takeItem(sourceList->currentRow()); + + if (!overrideSourceCount->isChecked()) + sourceCount->setText(QString().setNum(sourceList->count())); +} + +void GmpConfigForm::on_addGroupRecord_clicked() +{ + OstProto::Gmp::GroupRecord defRec; + QVariantMap grpRec; + QListWidgetItem *item = new QListWidgetItem; + + grpRec["groupRecordType"] = defRec.type(); + grpRec["groupRecordAddress"] = _defaultGroupIp; + grpRec["overrideGroupRecordSourceCount"] =defRec.is_override_source_count(); + grpRec["groupRecordSourceCount"] = defRec.source_count(); + grpRec["groupRecordSourceList"] = QStringList(); + grpRec["overrideAuxDataLength"] = defRec.is_override_aux_data_length(); + grpRec["auxDataLength"] = defRec.aux_data_length(); + grpRec["auxData"] = QByteArray().append( + QString().fromStdString(defRec.aux_data())); + + item->setData(Qt::UserRole, grpRec); + item->setText(QString("%1: %2") + .arg(groupRecordType->itemText(grpRec["groupRecordType"].toInt())) + .arg(grpRec["groupRecordAddress"].toString())); + + groupList->insertItem(groupList->currentRow(), item); + + if (!overrideGroupRecordCount->isChecked()) + groupRecordCount->setText(QString().setNum(groupList->count())); +} + +void GmpConfigForm::on_deleteGroupRecord_clicked() +{ + delete groupList->takeItem(groupList->currentRow()); + + if (!overrideGroupRecordCount->isChecked()) + groupRecordCount->setText(QString().setNum(groupList->count())); +} + +void GmpConfigForm::on_groupList_currentItemChanged(QListWidgetItem *current, + QListWidgetItem *previous) +{ + QVariantMap rec; + QStringList strList; + + qDebug("in %s", __FUNCTION__); + + // save previous record ... + if (previous == NULL) + goto _load_current_record; + + rec["groupRecordType"] = groupRecordType->currentIndex(); + rec["groupRecordAddress"] = groupRecordAddress->text(); + strList.clear(); + while (groupRecordSourceList->count()) + { + QListWidgetItem *item = groupRecordSourceList->takeItem(0); + strList.append(item->text()); + delete item; + } + rec["groupRecordSourceList"] = strList; + rec["overrideGroupRecordSourceCount"] = + overrideGroupRecordSourceCount->isChecked(); + rec["groupRecordSourceCount"] = groupRecordSourceCount->text().toUInt(); + rec["overrideAuxDataLength"] = overrideAuxDataLength->isChecked(); + rec["auxDataLength"] = auxDataLength->text().toUInt(); + rec["auxData"] = QByteArray().fromHex(QByteArray().append(auxData->text())); + + previous->setData(Qt::UserRole, rec); + previous->setText(QString("%1: %2") + .arg(groupRecordType->itemText(rec["groupRecordType"].toInt())) + .arg(rec["groupRecordAddress"].toString())); + +_load_current_record: + // ... and load current record + if (current == NULL) + goto _exit; + + rec = current->data(Qt::UserRole).toMap(); + + groupRecordType->setCurrentIndex(rec["groupRecordType"].toInt()); + groupRecordAddress->setText(rec["groupRecordAddress"].toString()); + strList = rec["groupRecordSourceList"].toStringList(); + groupRecordSourceList->clear(); + foreach (QString str, strList) + { + QListWidgetItem *item = new QListWidgetItem(str, groupRecordSourceList); + item->setFlags(item->flags() | Qt::ItemIsEditable); + } + overrideGroupRecordSourceCount->setChecked( + rec["overrideGroupRecordSourceCount"].toBool()); + groupRecordSourceCount->setText(QString().setNum( + rec["groupRecordSourceCount"].toUInt())); + overrideAuxDataLength->setChecked(rec["overrideAuxDataLength"].toBool()); + auxDataLength->setText(QString().setNum(rec["auxDataLength"].toUInt())); + auxData->setText(QString(rec["auxData"].toByteArray().toHex())); + +_exit: + groupRecord->setEnabled(current != NULL); + return; +} + +void GmpConfigForm::on_addGroupRecordSource_clicked() +{ + QListWidgetItem *item=new QListWidgetItem(_defaultSourceIp); + item->setFlags(item->flags() | Qt::ItemIsEditable); + groupRecordSourceList->insertItem(groupRecordSourceList->currentRow(),item); + + if (!overrideGroupRecordSourceCount->isChecked()) + groupRecordSourceCount->setText(QString().setNum( + groupRecordSourceList->count())); +} + +void GmpConfigForm::on_deleteGroupRecordSource_clicked() +{ + delete groupRecordSourceList->takeItem(groupRecordSourceList->currentRow()); + + if (!overrideGroupRecordSourceCount->isChecked()) + groupRecordSourceCount->setText(QString().setNum( + groupRecordSourceList->count())); +} + +void GmpConfigForm::on_auxData_textChanged(const QString &text) +{ + // auxDataLength is in units of words and each byte is 2 chars in text() + if (!overrideAuxDataLength->isChecked()) + auxDataLength->setText(QString().setNum((text.size()+7)/8)); +} diff --git a/common/gmpconfig.h b/common/gmpconfig.h new file mode 100644 index 0000000..70a833b --- /dev/null +++ b/common/gmpconfig.h @@ -0,0 +1,63 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _GMP_CONFIG_H +#define _GMP_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_gmp.h" + +class GmpConfigForm : + public AbstractProtocolConfigForm, + protected Ui::Gmp +{ + Q_OBJECT +public: + GmpConfigForm(QWidget *parent = 0); + ~GmpConfigForm(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +protected: + QString _defaultGroupIp; + QString _defaultSourceIp; + enum { + kSsmQueryPage = 0, + kSsmReportPage = 1 + }; + +private: + void update(); + +private slots: + void on_groupMode_currentIndexChanged(int index); + void on_addSource_clicked(); + void on_deleteSource_clicked(); + + void on_addGroupRecord_clicked(); + void on_deleteGroupRecord_clicked(); + void on_groupList_currentItemChanged(QListWidgetItem *current, + QListWidgetItem *previous); + void on_addGroupRecordSource_clicked(); + void on_deleteGroupRecordSource_clicked(); + void on_auxData_textChanged(const QString &text); +}; + +#endif diff --git a/common/hexdump.cpp b/common/hexdump.cpp new file mode 100644 index 0000000..376ccf0 --- /dev/null +++ b/common/hexdump.cpp @@ -0,0 +1,211 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "hexdump.h" +#include "streambase.h" + +HexDumpProtocol::HexDumpProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +HexDumpProtocol::~HexDumpProtocol() +{ +} + +AbstractProtocol* HexDumpProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new HexDumpProtocol(stream, parent); +} + +quint32 HexDumpProtocol::protocolNumber() const +{ + return OstProto::Protocol::kHexDumpFieldNumber; +} + +void HexDumpProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::hexDump)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void HexDumpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::hexDump)) + data.MergeFrom(protocol.GetExtension(OstProto::hexDump)); +} + +QString HexDumpProtocol::name() const +{ + return QString("HexDump"); +} + +QString HexDumpProtocol::shortName() const +{ + return QString("HexDump"); +} + +int HexDumpProtocol::fieldCount() const +{ + return hexDump_fieldCount; +} + +AbstractProtocol::FieldFlags HexDumpProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case hexDump_content: + flags |= FrameField; + break; + + case hexDump_pad_until_end: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +QVariant HexDumpProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case hexDump_content: + { + QByteArray ba; + QByteArray pad; + + switch(attrib) + { + case FieldValue: + case FieldTextValue: + case FieldFrameValue: + ba.append(QString().fromStdString(data.content())); + if (data.pad_until_end()) + { + pad = QByteArray( + protocolFrameSize(streamIndex) - ba.size(), '\0'); + } + break; + + default: + break; + } + + switch(attrib) + { + case FieldName: + return QString("Content"); + case FieldValue: + return ba; + case FieldTextValue: + return ba.append(pad).toHex(); + case FieldFrameValue: + return ba.append(pad); + default: + break; + } + break; + + } + + // Meta fields + case hexDump_pad_until_end: + { + switch(attrib) + { + case FieldValue: + return data.pad_until_end(); + default: + break; + } + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool HexDumpProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case hexDump_content: + { + QByteArray ba = value.toByteArray(); + data.set_content(ba.constData(), ba.size()); + isOk = true; + break; + } + case hexDump_pad_until_end: + { + bool pad = value.toBool(); + data.set_pad_until_end(pad); + isOk = true; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +int HexDumpProtocol::protocolFrameSize(int streamIndex) const +{ + int len = data.content().size(); + + if (data.pad_until_end()) + { + int pad = mpStream->frameLen(streamIndex) + - (protocolFrameOffset(streamIndex) + len + kFcsSize); + if (pad < 0) + pad = 0; + len += pad; + } + + return len; +} + diff --git a/common/hexdump.h b/common/hexdump.h new file mode 100644 index 0000000..4318192 --- /dev/null +++ b/common/hexdump.h @@ -0,0 +1,73 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _HEXDUMP_H +#define _HEXDUMP_H + +#include "abstractprotocol.h" +#include "hexdump.pb.h" + +/* +HexDump Protocol Frame Format - + +---------+---------+ + | User | Zero | + | HexDump | Padding | + +---------+---------+ +*/ + +class HexDumpProtocol : public AbstractProtocol +{ +public: + enum hexDumpfield + { + // Frame Fields + hexDump_content = 0, + + // Meta Fields + hexDump_pad_until_end, + + hexDump_fieldCount + }; + HexDumpProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~HexDumpProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual int protocolFrameSize(int streamIndex = 0) const; + +private: + OstProto::HexDump data; +}; +#endif diff --git a/common/hexdump.proto b/common/hexdump.proto new file mode 100644 index 0000000..6cdc3d5 --- /dev/null +++ b/common/hexdump.proto @@ -0,0 +1,32 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// HexDump Protocol +message HexDump { + optional bytes content = 1; + optional bool pad_until_end = 2 [default = true]; +} + +extend Protocol { + optional HexDump hexDump = 104; +} diff --git a/common/hexdump.ui b/common/hexdump.ui new file mode 100644 index 0000000..61f187a --- /dev/null +++ b/common/hexdump.ui @@ -0,0 +1,76 @@ + + HexDump + + + + 0 + 0 + 511 + 190 + + + + Form + + + + + + + + + Pad until end of packet + + + + + + + Qt::Horizontal + + + + 281 + 20 + + + + + + + + + 50 + 0 + + + + QFrame::Panel + + + QFrame::Sunken + + + 1 + + + + + + Qt::AlignCenter + + + + + + + + QHexEdit + QWidget +
qhexedit.h
+ 1 +
+
+ + +
diff --git a/common/hexdumpconfig.cpp b/common/hexdumpconfig.cpp new file mode 100644 index 0000000..1c057b6 --- /dev/null +++ b/common/hexdumpconfig.cpp @@ -0,0 +1,75 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "hexdumpconfig.h" +#include "hexdump.h" + +HexDumpConfigForm::HexDumpConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); + + hexEdit->setFont(QFont("Courier")); + hexEdit->setOverwriteMode(false); +} + +HexDumpConfigForm::~HexDumpConfigForm() +{ +} + +HexDumpConfigForm* HexDumpConfigForm::createInstance() +{ + return new HexDumpConfigForm; +} + +void HexDumpConfigForm::loadWidget(AbstractProtocol *proto) +{ + hexEdit->setData( + proto->fieldData( + HexDumpProtocol::hexDump_content, + AbstractProtocol::FieldValue + ).toByteArray()); + padUntilEnd->setChecked( + proto->fieldData( + HexDumpProtocol::hexDump_pad_until_end, + AbstractProtocol::FieldValue + ).toBool()); +} + +void HexDumpConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + HexDumpProtocol::hexDump_content, + hexEdit->data()); + proto->setFieldData( + HexDumpProtocol::hexDump_pad_until_end, + padUntilEnd->isChecked()); +} + +// +// ------------ private slots +// +void HexDumpConfigForm::on_hexEdit_overwriteModeChanged(bool isOverwriteMode) +{ + if (isOverwriteMode) + mode->setText(tr("Ovr")); + else + mode->setText(tr("Ins")); +} + diff --git a/common/hexdumpconfig.h b/common/hexdumpconfig.h new file mode 100644 index 0000000..b0dcfa7 --- /dev/null +++ b/common/hexdumpconfig.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _HEX_DUMP_CONFIG_H +#define _HEX_DUMP_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_hexdump.h" + +class HexDumpConfigForm : + public AbstractProtocolConfigForm, + private Ui::HexDump +{ + Q_OBJECT +public: + HexDumpConfigForm(QWidget *parent = 0); + virtual ~HexDumpConfigForm(); + + static HexDumpConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private slots: + void on_hexEdit_overwriteModeChanged(bool isOverwriteMode); +}; + +#endif diff --git a/common/icmp.cpp b/common/icmp.cpp new file mode 100644 index 0000000..651efb5 --- /dev/null +++ b/common/icmp.cpp @@ -0,0 +1,396 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "icmp.h" +#include "icmphelper.h" + +IcmpProtocol::IcmpProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +IcmpProtocol::~IcmpProtocol() +{ +} + +AbstractProtocol* IcmpProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new IcmpProtocol(stream, parent); +} + +quint32 IcmpProtocol::protocolNumber() const +{ + return OstProto::Protocol::kIcmpFieldNumber; +} + +void IcmpProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::icmp)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void IcmpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::icmp)) + data.MergeFrom(protocol.GetExtension(OstProto::icmp)); +} + +QString IcmpProtocol::name() const +{ + return QString("Internet Control Message Protocol"); +} + +QString IcmpProtocol::shortName() const +{ + return QString("ICMP"); +} + +quint32 IcmpProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdIp: + switch(icmpVersion()) + { + case OstProto::Icmp::kIcmp4: return 0x1; + case OstProto::Icmp::kIcmp6: return 0x3A; + default:break; + } + default:break; + } + + return AbstractProtocol::protocolId(type); +} + +int IcmpProtocol::fieldCount() const +{ + return icmp_fieldCount; +} + +int IcmpProtocol::frameFieldCount() const +{ + int count; + + if (isIdSeqType(icmpVersion(), icmpType())) + count = icmp_idSeqFrameFieldCount; + else + count = icmp_commonFrameFieldCount; + + return count; + +} + +AbstractProtocol::FieldFlags IcmpProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case icmp_type: + case icmp_code: + break; + + case icmp_checksum: + flags |= CksumField; + break; + + case icmp_identifier: + case icmp_sequence: + if (!isIdSeqType(icmpVersion(), icmpType())) + flags &= ~FrameField; + break; + + case icmp_version: + case icmp_is_override_checksum: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +QVariant IcmpProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case icmp_type: + { + unsigned char type = data.type() & 0xFF; + + switch(attrib) + { + case FieldName: + return QString("Type"); + case FieldValue: + return type; + case FieldTextValue: + return QString("%1").arg((uint) type); + case FieldFrameValue: + return QByteArray(1, type); + default: + break; + } + break; + + } + case icmp_code: + { + unsigned char code = data.code() & 0xFF; + + switch(attrib) + { + case FieldName: + return QString("Code"); + case FieldValue: + return code; + case FieldTextValue: + return QString("%1").arg((uint)code); + case FieldFrameValue: + return QByteArray(1, code); + default: + break; + } + break; + + } + case icmp_checksum: + { + quint16 cksum; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_checksum()) + { + cksum = data.checksum(); + } + else + { + quint16 cks; + quint32 sum = 0; + + cks = protocolFrameCksum(streamIndex, CksumIp); + sum += (quint16) ~cks; + cks = protocolFramePayloadCksum(streamIndex, CksumIp); + sum += (quint16) ~cks; + if (icmpVersion() == OstProto::Icmp::kIcmp6) + { + cks = protocolFrameHeaderCksum(streamIndex, + CksumIpPseudo); + sum += (quint16) ~cks; + } + + while(sum>>16) + sum = (sum & 0xFFFF) + (sum >> 16); + + cksum = (~sum) & 0xFFFF; + } + break; + default: + cksum = 0; // avoid the 'maybe used unitialized' warning + break; + } + + switch(attrib) + { + case FieldName: + return QString("Checksum"); + case FieldValue: + return cksum; + case FieldFrameValue: + { + QByteArray fv; + + fv.resize(2); + qToBigEndian(cksum, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QString("0x%1").arg( + cksum, 4, BASE_HEX, QChar('0'));; + case FieldBitSize: + return 16; + default: + break; + } + break; + } + case icmp_identifier: + { + switch(attrib) + { + case FieldName: + return QString("Identifier"); + case FieldValue: + return data.identifier(); + case FieldTextValue: + return QString("%1").arg(data.identifier()); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.identifier(), + (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case icmp_sequence: + { + switch(attrib) + { + case FieldName: + return QString("Sequence"); + case FieldValue: + return data.sequence(); + case FieldTextValue: + return QString("%1").arg(data.sequence()); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.sequence(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + + + // Meta fields + case icmp_version: + { + switch(attrib) + { + case FieldValue: + return data.icmp_version(); + default: + break; + } + break; + } + case icmp_is_override_checksum: + { + switch(attrib) + { + case FieldValue: + return data.is_override_checksum(); + default: + break; + } + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool IcmpProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case icmp_type: + { + uint type = value.toUInt(&isOk); + if (isOk) + data.set_type(type & 0xFF); + break; + } + case icmp_code: + { + uint code = value.toUInt(&isOk); + if (isOk) + data.set_code(code & 0xFF); + break; + } + case icmp_checksum: + { + uint csum = value.toUInt(&isOk); + if (isOk) + data.set_checksum(csum); + break; + } + case icmp_identifier: + { + uint id = value.toUInt(&isOk); + if (isOk) + data.set_identifier(id); + break; + } + case icmp_sequence: + { + uint seq = value.toUInt(&isOk); + if (isOk) + data.set_sequence(seq); + break; + } + case icmp_version: + { + int ver = value.toUInt(&isOk); + if (isOk) + data.set_icmp_version(OstProto::Icmp::Version(ver)); + break; + } + case icmp_is_override_checksum: + { + bool ovr = value.toBool(); + data.set_is_override_checksum(ovr); + isOk = true; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + + + diff --git a/common/icmp.h b/common/icmp.h new file mode 100644 index 0000000..b24f025 --- /dev/null +++ b/common/icmp.h @@ -0,0 +1,96 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ICMP_H +#define _ICMP_H + +#include "abstractprotocol.h" +#include "icmp.pb.h" + +/* +Icmp Protocol Frame Format - + +-----+------+------+------+-------+ + | TYP | CODE | CSUM | [ID] | [SEQ] | + | (1) | (1) | (2) | (2) | (2) | + +-----+------+------+------+-------+ +Fields within [] are applicable only to certain TYPEs +Figures in braces represent field width in bytes +*/ + +class IcmpProtocol : public AbstractProtocol +{ +public: + enum icmpfield + { + // Frame Fields + icmp_type = 0, + icmp_code, + icmp_checksum, + icmp_commonFrameFieldCount, + + icmp_identifier = icmp_commonFrameFieldCount, + icmp_sequence, + icmp_idSeqFrameFieldCount, + + // Meta Fields + icmp_is_override_checksum = icmp_idSeqFrameFieldCount, + icmp_version, + + icmp_fieldCount + }; + + IcmpProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~IcmpProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + virtual int frameFieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + +private: + OstProto::Icmp data; + + OstProto::Icmp::Version icmpVersion() const + { + return OstProto::Icmp::Version( + fieldData(icmp_version, FieldValue).toUInt()); + } + int icmpType() const + { + return fieldData(icmp_type, FieldValue).toInt(); + } +}; + +#endif diff --git a/common/icmp.proto b/common/icmp.proto new file mode 100644 index 0000000..6abc686 --- /dev/null +++ b/common/icmp.proto @@ -0,0 +1,44 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Icmp Protocol +message Icmp { + + enum Version { + kIcmp4 = 4; + kIcmp6 = 6; + } + + optional Version icmp_version = 1 [default = kIcmp4]; + optional bool is_override_checksum = 2; + + optional uint32 type = 6 [default = 0x8]; // echo request + optional uint32 code = 7; + optional uint32 checksum = 8; + optional uint32 identifier = 9 [default = 1234]; + optional uint32 sequence = 10; +} + +extend Protocol { + optional Icmp icmp = 402; +} diff --git a/common/icmp.ui b/common/icmp.ui new file mode 100644 index 0000000..7ba1938 --- /dev/null +++ b/common/icmp.ui @@ -0,0 +1,178 @@ + + Icmp + + + + 0 + 0 + 373 + 166 + + + + Form + + + + + + Version + + + + + + ICMPv4 + + + + + + + ICMPv6 + + + + + + + + + + Type + + + typeCombo + + + + + + + + + + Code + + + codeEdit + + + + + + + + + + Qt::Horizontal + + + + 31 + 20 + + + + + + + + Checksum + + + + + + + false + + + + + + + + + + + + + Identifier + + + idEdit + + + + + + + + + + Sequence + + + seqEdit + + + + + + + + + + + + + Qt::Vertical + + + + 211 + 71 + + + + + + + + + IntComboBox + QComboBox +
intcombobox.h
+
+
+ + icmp4Button + icmp6Button + typeCombo + codeEdit + overrideCksum + cksumEdit + idEdit + seqEdit + + + + + overrideCksum + toggled(bool) + cksumEdit + setEnabled(bool) + + + 33 + 70 + + + 96 + 71 + + + + +
diff --git a/common/icmp6pdml.cpp b/common/icmp6pdml.cpp new file mode 100644 index 0000000..53c4a34 --- /dev/null +++ b/common/icmp6pdml.cpp @@ -0,0 +1,100 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "icmp6pdml.h" + +#include "icmp.pb.h" +#include "sample.pb.h" + +PdmlIcmp6Protocol::PdmlIcmp6Protocol() +{ + ostProtoId_ = OstProto::Protocol::kSampleFieldNumber; + + proto_ = NULL; +} + +PdmlProtocol* PdmlIcmp6Protocol::createInstance() +{ + return new PdmlIcmp6Protocol(); +} + +void PdmlIcmp6Protocol::preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, + int expectedPos, OstProto::Protocol *pbProto, + OstProto::Stream *stream) +{ + proto_ = NULL; + ostProtoId_ = OstProto::Protocol::kSampleFieldNumber; + icmp_.preProtocolHandler(name, attributes, expectedPos, pbProto, stream); + mld_.preProtocolHandler(name, attributes, expectedPos, pbProto, stream); +} + +void PdmlIcmp6Protocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream) +{ + if (proto_) + proto_->postProtocolHandler(pbProto, stream); + else + stream->mutable_protocol()->RemoveLast(); + + proto_ = NULL; + ostProtoId_ = OstProto::Protocol::kSampleFieldNumber; +} + +void PdmlIcmp6Protocol::unknownFieldHandler(QString name, + int pos, int size, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream) +{ + if (proto_) + { + proto_->unknownFieldHandler(name, pos, size, attributes, pbProto, + stream); + } + else if (name == "icmpv6.type") + { + bool isOk; + uint type = attributes.value("value").toString().toUInt( + &isOk, kBaseHex); + + if (((type >= 130) && (type <= 132)) || (type == 143)) + { + // MLD + proto_ = &mld_; + fieldMap_ = mld_.fieldMap_; + ostProtoId_ = OstProto::Protocol::kMldFieldNumber; + } + else + { + // ICMP + proto_ = &icmp_; + fieldMap_ = icmp_.fieldMap_; + ostProtoId_ = OstProto::Protocol::kIcmpFieldNumber; + } + + pbProto->mutable_protocol_id()->set_id(ostProtoId_); + pbProto->MutableExtension(OstProto::sample)->Clear(); + + fieldHandler(name, attributes, pbProto, stream); + } + else + { + qDebug("unexpected field %s", name.toAscii().constData()); + } +} + diff --git a/common/icmp6pdml.h b/common/icmp6pdml.h new file mode 100644 index 0000000..d159824 --- /dev/null +++ b/common/icmp6pdml.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ICMP6_PDML_H +#define _ICMP6_PDML_H + +#include "pdmlprotocol.h" + +#include "icmppdml.h" +#include "mldpdml.h" + +class PdmlIcmp6Protocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); + + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); +protected: + PdmlIcmp6Protocol(); +private: + PdmlIcmpProtocol icmp_; + PdmlMldProtocol mld_; + PdmlProtocol *proto_; +}; + +#endif diff --git a/common/icmpconfig.cpp b/common/icmpconfig.cpp new file mode 100644 index 0000000..2bf65af --- /dev/null +++ b/common/icmpconfig.cpp @@ -0,0 +1,191 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "icmpconfig.h" + +#include "icmp.h" +#include "icmphelper.h" + +#include + +IcmpConfigForm::IcmpConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + versionGroup = new QButtonGroup(this); + setupUi(this); + + // auto-connect's not working, for some reason I can't figure out! + // slot name changed to when_ instead of on_ so that connectSlotsByName() + // doesn't complain + connect(versionGroup, + SIGNAL(buttonClicked(int)), + SLOT(when_versionGroup_buttonClicked(int))); + + versionGroup->addButton(icmp4Button, OstProto::Icmp::kIcmp4); + versionGroup->addButton(icmp6Button, OstProto::Icmp::kIcmp6); + + typeCombo->setValidator(new QIntValidator(0, 0xFF, this)); + + icmp4Button->click(); + + idEdit->setValidator(new QIntValidator(0, 0xFFFF, this)); + seqEdit->setValidator(new QIntValidator(0, 0xFFFF, this)); +} + +IcmpConfigForm::~IcmpConfigForm() +{ +} + +IcmpConfigForm* IcmpConfigForm::createInstance() +{ + return new IcmpConfigForm; +} + +void IcmpConfigForm::loadWidget(AbstractProtocol *proto) +{ + versionGroup->button( + proto->fieldData( + IcmpProtocol::icmp_version, + AbstractProtocol::FieldValue + ).toUInt())->click(); + + typeCombo->setValue( + proto->fieldData( + IcmpProtocol::icmp_type, + AbstractProtocol::FieldValue + ).toUInt()); + codeEdit->setText( + proto->fieldData( + IcmpProtocol::icmp_code, + AbstractProtocol::FieldValue + ).toString()); + + overrideCksum->setChecked( + proto->fieldData( + IcmpProtocol::icmp_is_override_checksum, + AbstractProtocol::FieldValue + ).toBool()); + cksumEdit->setText(uintToHexStr( + proto->fieldData( + IcmpProtocol::icmp_checksum, + AbstractProtocol::FieldValue + ).toUInt(), 2)); + + idEdit->setText( + proto->fieldData( + IcmpProtocol::icmp_identifier, + AbstractProtocol::FieldValue + ).toString()); + seqEdit->setText( + proto->fieldData( + IcmpProtocol::icmp_sequence, + AbstractProtocol::FieldValue + ).toString()); +} + +void IcmpConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + IcmpProtocol::icmp_version, + versionGroup->checkedId()); + + proto->setFieldData( + IcmpProtocol::icmp_type, + typeCombo->currentValue()); + proto->setFieldData( + IcmpProtocol::icmp_code, + codeEdit->text()); + + proto->setFieldData( + IcmpProtocol::icmp_is_override_checksum, + overrideCksum->isChecked()); + proto->setFieldData( + IcmpProtocol::icmp_checksum, + hexStrToUInt(cksumEdit->text())); + + proto->setFieldData( + IcmpProtocol::icmp_identifier, + idEdit->text()); + proto->setFieldData( + IcmpProtocol::icmp_sequence, + seqEdit->text()); +} + +// +// -------- private slots +// +void IcmpConfigForm::on_typeCombo_currentIndexChanged(int /*index*/) +{ + idSeqFrame->setVisible( + isIdSeqType( + OstProto::Icmp::Version(versionGroup->checkedId()), + typeCombo->currentValue())); +} + +void IcmpConfigForm::when_versionGroup_buttonClicked(int id) +{ + int value = typeCombo->currentValue(); + + typeCombo->clear(); + + switch(id) + { + case OstProto::Icmp::kIcmp4: + typeCombo->addItem(kIcmpEchoReply, "Echo Reply"); + typeCombo->addItem(kIcmpDestinationUnreachable, + "Destination Unreachable"); + typeCombo->addItem(kIcmpSourceQuench, "Source Quench"); + typeCombo->addItem(kIcmpRedirect, "Redirect"); + typeCombo->addItem(kIcmpEchoRequest, "Echo Request"); + typeCombo->addItem(kIcmpTimeExceeded, "Time Exceeded"); + typeCombo->addItem(kIcmpParameterProblem, "Parameter Problem"); + typeCombo->addItem(kIcmpTimestampRequest, "Timestamp Request"); + typeCombo->addItem(kIcmpTimestampReply, "Timestamp Reply"); + typeCombo->addItem(kIcmpInformationRequest, "Information Request"); + typeCombo->addItem(kIcmpInformationReply, "Information Reply"); + typeCombo->addItem(kIcmpAddressMaskRequest, "Address Mask Request"); + typeCombo->addItem(kIcmpAddressMaskReply, "Address Mask Reply"); + break; + + case OstProto::Icmp::kIcmp6: + typeCombo->addItem(kIcmp6DestinationUnreachable, + "Destination Unreachable"); + typeCombo->addItem(kIcmp6PacketTooBig, "Packet Too Big"); + typeCombo->addItem(kIcmp6TimeExceeded, "Time Exceeded"); + typeCombo->addItem(kIcmp6ParameterProblem, "Parameter Problem"); + + typeCombo->addItem(kIcmp6EchoRequest, "Echo Request"); + typeCombo->addItem(kIcmp6EchoReply, "Echo Reply"); + typeCombo->addItem(kIcmp6RouterSolicitation, "Router Solicitation"); + typeCombo->addItem(kIcmp6RouterAdvertisement, "Router Advertisement"); + typeCombo->addItem(kIcmp6NeighbourSolicitation, + "Neighbour Solicitation"); + typeCombo->addItem(kIcmp6NeighbourAdvertisement, + "Neighbour Advertisement"); + typeCombo->addItem(kIcmp6Redirect, "Redirect"); + typeCombo->addItem(kIcmp6InformationQuery, "Information Query"); + typeCombo->addItem(kIcmp6InformationResponse, "Information Response"); + break; + default: + Q_ASSERT(false); + } + + typeCombo->setValue(value); +} + diff --git a/common/icmpconfig.h b/common/icmpconfig.h new file mode 100644 index 0000000..6e01065 --- /dev/null +++ b/common/icmpconfig.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ICMP_CONFIG_H +#define _ICMP_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_icmp.h" + +class QButtonGroup; + +class IcmpConfigForm : + public AbstractProtocolConfigForm, + private Ui::Icmp +{ + Q_OBJECT +public: + IcmpConfigForm(QWidget *parent = 0); + virtual ~IcmpConfigForm(); + + static IcmpConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private: + QButtonGroup *versionGroup; + +private slots: + void on_typeCombo_currentIndexChanged(int index); + void when_versionGroup_buttonClicked(int id); +}; + +#endif diff --git a/common/icmphelper.h b/common/icmphelper.h new file mode 100644 index 0000000..0c5bcf7 --- /dev/null +++ b/common/icmphelper.h @@ -0,0 +1,88 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ICMP_HELPER_H +#define _ICMP_HELPER_H + +#include "icmp.pb.h" + +#include + +enum IcmpType +{ + kIcmpEchoReply = 0, + kIcmpDestinationUnreachable = 3, + kIcmpSourceQuench = 4, + kIcmpRedirect = 5, + kIcmpEchoRequest = 8, + kIcmpTimeExceeded = 11, + kIcmpParameterProblem = 12, + kIcmpTimestampRequest = 13, + kIcmpTimestampReply = 14, + kIcmpInformationRequest = 15, + kIcmpInformationReply = 16, + kIcmpAddressMaskRequest = 17, + kIcmpAddressMaskReply = 18 +}; + +enum Icmp6Type +{ + kIcmp6DestinationUnreachable = 1, + kIcmp6PacketTooBig = 2, + kIcmp6TimeExceeded = 3, + kIcmp6ParameterProblem = 4, + kIcmp6EchoRequest = 128, + kIcmp6EchoReply = 129, + kIcmp6RouterSolicitation = 133, + kIcmp6RouterAdvertisement = 134, + kIcmp6NeighbourSolicitation = 135, + kIcmp6NeighbourAdvertisement = 136, + kIcmp6Redirect = 137, + kIcmp6InformationQuery = 139, + kIcmp6InformationResponse = 140 +}; + +static QSet icmpIdSeqSet = QSet() + << kIcmpEchoRequest + << kIcmpEchoReply + << kIcmpInformationRequest + << kIcmpInformationReply; + +static QSet icmp6IdSeqSet = QSet() + << kIcmp6EchoRequest + << kIcmp6EchoReply; + +bool inline isIdSeqType(OstProto::Icmp::Version ver, int type) +{ + //qDebug("%s: ver = %d, type = %d", __FUNCTION__, ver, type); + switch(ver) + { + case OstProto::Icmp::kIcmp4: + return icmpIdSeqSet.contains(type); + case OstProto::Icmp::kIcmp6: + return icmp6IdSeqSet.contains(type); + default: + break; + } + + Q_ASSERT(false); // unreachable + return false; +} + +#endif diff --git a/common/icmppdml.cpp b/common/icmppdml.cpp new file mode 100644 index 0000000..0bff798 --- /dev/null +++ b/common/icmppdml.cpp @@ -0,0 +1,93 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "icmppdml.h" + +#include "icmp.pb.h" + +PdmlIcmpProtocol::PdmlIcmpProtocol() +{ + ostProtoId_ = OstProto::Protocol::kIcmpFieldNumber; + + fieldMap_.insert("icmp.type", OstProto::Icmp::kTypeFieldNumber); + fieldMap_.insert("icmp.code", OstProto::Icmp::kCodeFieldNumber); + fieldMap_.insert("icmp.checksum", OstProto::Icmp::kChecksumFieldNumber); + fieldMap_.insert("icmp.ident", OstProto::Icmp::kIdentifierFieldNumber); + fieldMap_.insert("icmp.seq", OstProto::Icmp::kSequenceFieldNumber); + + fieldMap_.insert("icmpv6.type", OstProto::Icmp::kTypeFieldNumber); + fieldMap_.insert("icmpv6.code", OstProto::Icmp::kCodeFieldNumber); + fieldMap_.insert("icmpv6.checksum", OstProto::Icmp::kChecksumFieldNumber); + fieldMap_.insert("icmpv6.echo.identifier", + OstProto::Icmp::kIdentifierFieldNumber); + fieldMap_.insert("icmpv6.echo.sequence_number", + OstProto::Icmp::kSequenceFieldNumber); +} + +PdmlProtocol* PdmlIcmpProtocol::createInstance() +{ + return new PdmlIcmpProtocol(); +} + +void PdmlIcmpProtocol::preProtocolHandler(QString name, + const QXmlStreamAttributes& /*attributes*/, int /*expectedPos*/, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + OstProto::Icmp *icmp = pbProto->MutableExtension(OstProto::icmp); + + if (name == "icmp") + icmp->set_icmp_version(OstProto::Icmp::kIcmp4); + else if (name == "icmpv6") + icmp->set_icmp_version(OstProto::Icmp::kIcmp6); + + icmp->set_is_override_checksum(true); + + icmp->set_type(kIcmpInvalidType); +} + +void PdmlIcmpProtocol::unknownFieldHandler(QString /*name*/, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + bool isOk; + OstProto::Icmp *icmp = pbProto->MutableExtension(OstProto::icmp); + + if ((icmp->icmp_version() == OstProto::Icmp::kIcmp6) + && (icmp->type() >= kIcmp6EchoRequest) + && (icmp->type() <= kIcmp6EchoReply)) + { + QString addrHexStr = attributes.value("value").toString(); + + // Wireshark 1.4.x does not have these as filterable fields + if (attributes.value("show").toString().startsWith("ID")) + icmp->set_identifier(addrHexStr.toUInt(&isOk, kBaseHex)); + else if (attributes.value("show").toString().startsWith("Sequence")) + icmp->set_sequence(addrHexStr.toUInt(&isOk, kBaseHex)); + } +} + +void PdmlIcmpProtocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream) +{ + OstProto::Icmp *icmp = pbProto->MutableExtension(OstProto::icmp); + + if (icmp->type() == kIcmpInvalidType) + stream->mutable_protocol()->RemoveLast(); +} + diff --git a/common/icmppdml.h b/common/icmppdml.h new file mode 100644 index 0000000..58b3e37 --- /dev/null +++ b/common/icmppdml.h @@ -0,0 +1,48 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _ICMP_PDML_H +#define _ICMP_PDML_H + +#include "pdmlprotocol.h" + +class PdmlIcmpProtocol : public PdmlProtocol +{ + friend class PdmlIcmp6Protocol; +public: + static PdmlProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); +protected: + PdmlIcmpProtocol(); +private: + static const uint kIcmpInvalidType = 0xFFFFFFFF; + + static const uint kIcmp6EchoRequest = 128; + static const uint kIcmp6EchoReply = 129; +}; + +#endif diff --git a/common/igmp.cpp b/common/igmp.cpp new file mode 100644 index 0000000..8a5a0b9 --- /dev/null +++ b/common/igmp.cpp @@ -0,0 +1,364 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "igmp.h" +#include "iputils.h" + +#include +#include + +IgmpProtocol::IgmpProtocol(StreamBase *stream, AbstractProtocol *parent) + : GmpProtocol(stream, parent) +{ + _hasPayload = false; + + data.set_type(kIgmpV2Query); +} + +IgmpProtocol::~IgmpProtocol() +{ +} + +AbstractProtocol* IgmpProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new IgmpProtocol(stream, parent); +} + +quint32 IgmpProtocol::protocolNumber() const +{ + return OstProto::Protocol::kIgmpFieldNumber; +} + +void IgmpProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::igmp)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void IgmpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::igmp)) + data.MergeFrom(protocol.GetExtension(OstProto::igmp)); +} + +QString IgmpProtocol::name() const +{ + return QString("Internet Group Management Protocol"); +} + +QString IgmpProtocol::shortName() const +{ + return QString("IGMP"); +} + +quint32 IgmpProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdIp: return 0x2; + default:break; + } + + return AbstractProtocol::protocolId(type); +} + +QVariant IgmpProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case kRsvdMrtCode: + { + uint mrt = 0; + quint8 mrcode = 0; + + if (msgType() == kIgmpV3Query) + { + mrt = data.max_response_time(); + mrcode = quint8(mrc(mrt)); + } + else if (msgType() == kIgmpV2Query) + { + mrt = data.max_response_time(); + mrcode = mrt & 0xFF; + } + + + switch(attrib) + { + case FieldName: + if (isQuery()) + return QString("Max Response Time"); + else + return QString("Reserved"); + case FieldValue: + return mrt; + case FieldTextValue: + return QString("%1").arg(mrt); + case FieldFrameValue: + return QByteArray(1, mrcode); + default: + break; + } + break; + } + case kGroupAddress: + { + quint32 grpIp = ipUtils::ipAddress( + data.group_address().v4(), + data.group_prefix(), + ipUtils::AddrMode(data.group_mode()), + data.group_count(), + streamIndex); + + switch(attrib) + { + case FieldName: + return QString("Group Address"); + case FieldValue: + case FieldTextValue: + return QHostAddress(grpIp).toString(); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian(grpIp, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case kSources: + { + switch(attrib) + { + case FieldName: + return QString("Source List"); + case FieldValue: + { + QStringList list; + + for (int i = 0; i < data.sources_size(); i++) + list.append(QHostAddress(data.sources(i).v4()).toString()); + return list; + } + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4 * data.sources_size()); + for (int i = 0; i < data.sources_size(); i++) + qToBigEndian(data.sources(i).v4(), (uchar*)(fv.data()+4*i)); + return fv; + } + case FieldTextValue: + { + QStringList list; + + for (int i = 0; i < data.sources_size(); i++) + list.append(QHostAddress(data.sources(i).v4()).toString()); + return list.join(", "); + } + default: + break; + } + break; + } + case kGroupRecords: + { + switch(attrib) + { + case FieldValue: + { + QVariantList grpRecords = GmpProtocol::fieldData( + index, attrib, streamIndex).toList(); + + for (int i = 0; i < data.group_records_size(); i++) + { + QVariantMap grpRec = grpRecords.at(i).toMap(); + OstProto::Gmp::GroupRecord rec = data.group_records(i); + + grpRec["groupRecordAddress"] = QHostAddress( + rec.group_address().v4()).toString(); + + QStringList sl; + for (int j = 0; j < rec.sources_size(); j++) + sl.append(QHostAddress(rec.sources(j).v4()).toString()); + grpRec["groupRecordSourceList"] = sl; + + grpRecords.replace(i, grpRec); + } + return grpRecords; + } + case FieldFrameValue: + { + QVariantList list = GmpProtocol::fieldData( + index, attrib, streamIndex).toList(); + QByteArray fv; + + for (int i = 0; i < data.group_records_size(); i++) + { + OstProto::Gmp::GroupRecord rec = data.group_records(i); + QByteArray rv = list.at(i).toByteArray(); + + rv.insert(4, QByteArray(4+4*rec.sources_size(), char(0))); + qToBigEndian(rec.group_address().v4(), + (uchar*)(rv.data()+4)); + for (int j = 0; j < rec.sources_size(); j++) + { + qToBigEndian(rec.sources(j).v4(), + (uchar*)(rv.data()+8+4*j)); + } + + fv.append(rv); + } + return fv; + } + case FieldTextValue: + { + QStringList list = GmpProtocol::fieldData( + index, attrib, streamIndex).toStringList(); + + for (int i = 0; i < data.group_records_size(); i++) + { + OstProto::Gmp::GroupRecord rec = data.group_records(i); + QString recStr = list.at(i); + QString str; + + str.append(QString("Group: %1").arg( + QHostAddress(rec.group_address().v4()).toString())); + + str.append("; Sources: "); + QStringList sl; + for (int j = 0; j < rec.sources_size(); j++) + sl.append(QHostAddress(rec.sources(j).v4()).toString()); + str.append(sl.join(", ")); + + recStr.replace("XXX", str); + list.replace(i, recStr); + } + return list.join("\n").insert(0, "\n"); + } + default: + break; + } + break; + } + default: + break; + } + + return GmpProtocol::fieldData(index, attrib, streamIndex); +} + +bool IgmpProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case kRsvdMrtCode: + { + uint mrt = value.toUInt(&isOk); + if (isOk) + data.set_max_response_time(mrt); + break; + } + case kGroupAddress: + { + QHostAddress addr(value.toString()); + quint32 ip = addr.toIPv4Address(); + isOk = (addr.protocol() == QAbstractSocket::IPv4Protocol); + if (isOk) + data.mutable_group_address()->set_v4(ip); + break; + } + case kSources: + { + QStringList list = value.toStringList(); + + data.clear_sources(); + foreach(QString str, list) + { + quint32 ip = QHostAddress(str).toIPv4Address(); + data.add_sources()->set_v4(ip); + } + break; + } + + case kGroupRecords: + { + GmpProtocol::setFieldData(index, value, attrib); + QVariantList list = value.toList(); + + for (int i = 0; i < list.count(); i++) + { + QVariantMap grpRec = list.at(i).toMap(); + OstProto::Gmp::GroupRecord *rec = data.mutable_group_records(i); + + rec->mutable_group_address()->set_v4(QHostAddress( + grpRec["groupRecordAddress"].toString()) + .toIPv4Address()); + + QStringList srcList = grpRec["groupRecordSourceList"] + .toStringList(); + rec->clear_sources(); + foreach (QString src, srcList) + { + rec->add_sources()->set_v4( + QHostAddress(src).toIPv4Address()); + } + } + + break; + } + + default: + isOk = GmpProtocol::setFieldData(index, value, attrib); + break; + } + +_exit: + return isOk; +} + +quint16 IgmpProtocol::checksum(int streamIndex) const +{ + quint16 cks; + quint32 sum = 0; + + // TODO: add as a new CksumType (CksumIgmp?) and implement in AbsProto + cks = protocolFrameCksum(streamIndex, CksumIp); + sum += (quint16) ~cks; + cks = protocolFramePayloadCksum(streamIndex, CksumIp); + sum += (quint16) ~cks; + while (sum >> 16) + sum = (sum & 0xFFFF) + (sum >> 16); + + cks = (~sum) & 0xFFFF; + + return cks; +} diff --git a/common/igmp.h b/common/igmp.h new file mode 100644 index 0000000..0634a1f --- /dev/null +++ b/common/igmp.h @@ -0,0 +1,98 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _IGMP_H +#define _IGMP_H + +#include "gmp.h" +#include "igmp.pb.h" + +// IGMP uses the same msg type value for 'Query' messages across +// versions despite the fields being different. To distinguish +// Query messages of different versions, we use an additional +// upper byte +enum IgmpMsgType +{ + kIgmpV1Query = 0x11, + kIgmpV1Report = 0x12, + + kIgmpV2Query = 0xFF11, + kIgmpV2Report = 0x16, + kIgmpV2Leave = 0x17, + + kIgmpV3Query = 0xFE11, + kIgmpV3Report = 0x22, +}; + +class IgmpProtocol : public GmpProtocol +{ +public: + IgmpProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~IgmpProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual QString name() const; + virtual QString shortName() const; + + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + +protected: + virtual bool isSsmReport() const; + virtual bool isQuery() const; + virtual bool isSsmQuery() const; + + virtual quint16 checksum(int streamIndex) const; + +private: + int mrc(int value) const; +}; + +inline bool IgmpProtocol::isSsmReport() const +{ + return (msgType() == kIgmpV3Report); +} + +inline bool IgmpProtocol::isQuery() const +{ + return ((msgType() == kIgmpV1Query) + || (msgType() == kIgmpV2Query) + || (msgType() == kIgmpV3Query)); +} + +inline bool IgmpProtocol::isSsmQuery() const +{ + return (msgType() == kIgmpV3Query); +} + +inline int IgmpProtocol::mrc(int value) const +{ + return quint8(value); // TODO: if value > 128, convert to mantissa/exp form +} + +#endif diff --git a/common/igmp.proto b/common/igmp.proto new file mode 100755 index 0000000..a6f005c --- /dev/null +++ b/common/igmp.proto @@ -0,0 +1,27 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; +import "gmp.proto"; + +package OstProto; + +extend Protocol { + optional Gmp igmp = 403; +} diff --git a/common/igmpconfig.cpp b/common/igmpconfig.cpp new file mode 100644 index 0000000..743e9a3 --- /dev/null +++ b/common/igmpconfig.cpp @@ -0,0 +1,112 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "igmpconfig.h" +#include "igmp.h" +#include "ipv4addressdelegate.h" + +IgmpConfigForm::IgmpConfigForm(QWidget *parent) + : GmpConfigForm(parent) +{ + connect(msgTypeCombo, SIGNAL(currentIndexChanged(int)), + SLOT(on_msgTypeCombo_currentIndexChanged(int))); + + msgTypeCombo->setValueMask(0xFF); + msgTypeCombo->addItem(kIgmpV1Query, "IGMPv1 Query"); + msgTypeCombo->addItem(kIgmpV1Report, "IGMPv1 Report"); + msgTypeCombo->addItem(kIgmpV2Query, "IGMPv2 Query"); + msgTypeCombo->addItem(kIgmpV2Report, "IGMPv2 Report"); + msgTypeCombo->addItem(kIgmpV2Leave, "IGMPv2 Leave"); + msgTypeCombo->addItem(kIgmpV3Query, "IGMPv3 Query"); + msgTypeCombo->addItem(kIgmpV3Report, "IGMPv3 Report"); + + _defaultGroupIp = "0.0.0.0"; + _defaultSourceIp = "0.0.0.0"; + + groupAddress->setInputMask("009.009.009.009;"); // FIXME: use validator + groupRecordAddress->setInputMask("009.009.009.009;"); // FIXME:use validator + sourceList->setItemDelegate(new IPv4AddressDelegate(this)); + groupRecordSourceList->setItemDelegate(new IPv4AddressDelegate(this)); +} + +IgmpConfigForm::~IgmpConfigForm() +{ +} + +IgmpConfigForm* IgmpConfigForm::createInstance() +{ + return new IgmpConfigForm; +} + +void IgmpConfigForm::loadWidget(AbstractProtocol *proto) +{ + GmpConfigForm::loadWidget(proto); + + maxResponseTime->setText( + proto->fieldData( + IgmpProtocol::kRsvdMrtCode, + AbstractProtocol::FieldValue + ).toString()); +} + +void IgmpConfigForm::storeWidget(AbstractProtocol *proto) +{ + GmpConfigForm::storeWidget(proto); + + proto->setFieldData( + IgmpProtocol::kRsvdMrtCode, + maxResponseTime->text()); +} + +// +// -- private slots +// + +void IgmpConfigForm::on_msgTypeCombo_currentIndexChanged(int /*index*/) +{ + switch(msgTypeCombo->currentValue()) + { + case kIgmpV1Query: + case kIgmpV1Report: + case kIgmpV2Query: + case kIgmpV2Report: + case kIgmpV2Leave: + asmGroup->show(); + ssmWidget->hide(); + break; + + case kIgmpV3Query: + asmGroup->show(); + ssmWidget->setCurrentIndex(kSsmQueryPage); + ssmWidget->show(); + break; + + case kIgmpV3Report: + asmGroup->hide(); + ssmWidget->setCurrentIndex(kSsmReportPage); + ssmWidget->show(); + break; + + default: + asmGroup->hide(); + ssmWidget->hide(); + break; + } +} + diff --git a/common/igmpconfig.h b/common/igmpconfig.h new file mode 100644 index 0000000..6428db1 --- /dev/null +++ b/common/igmpconfig.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _IGMP_CONFIG_H +#define _IGMP_CONFIG_H + +#include "gmpconfig.h" + +class IgmpConfigForm : public GmpConfigForm +{ + Q_OBJECT +public: + IgmpConfigForm(QWidget *parent = 0); + virtual ~IgmpConfigForm(); + + static IgmpConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private slots: + void on_msgTypeCombo_currentIndexChanged(int index); +}; + +#endif diff --git a/common/igmppdml.cpp b/common/igmppdml.cpp new file mode 100644 index 0000000..19516d7 --- /dev/null +++ b/common/igmppdml.cpp @@ -0,0 +1,141 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "igmppdml.h" + +#include "igmp.pb.h" + +PdmlIgmpProtocol::PdmlIgmpProtocol() +{ + ostProtoId_ = OstProto::Protocol::kIgmpFieldNumber; + + fieldMap_.insert("igmp.max_resp", + OstProto::Gmp::kMaxResponseTimeFieldNumber); // FIXME + fieldMap_.insert("igmp.checksum", OstProto::Gmp::kChecksumFieldNumber); + + fieldMap_.insert("igmp.s", OstProto::Gmp::kSFlagFieldNumber); + fieldMap_.insert("igmp.qrv", OstProto::Gmp::kQrvFieldNumber); + fieldMap_.insert("igmp.qqic", OstProto::Gmp::kQqiFieldNumber); // FIXME + + fieldMap_.insert("igmp.num_grp_recs", + OstProto::Gmp::kGroupRecordCountFieldNumber); +} + +PdmlProtocol* PdmlIgmpProtocol::createInstance() +{ + return new PdmlIgmpProtocol(); +} + +void PdmlIgmpProtocol::preProtocolHandler(QString /*name*/, + const QXmlStreamAttributes& /*attributes*/, int /*expectedPos*/, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + OstProto::Gmp *igmp = pbProto->MutableExtension(OstProto::igmp); + + igmp->set_is_override_rsvd_code(true); + igmp->set_is_override_checksum(true); + igmp->set_is_override_source_count(true); + igmp->set_is_override_group_record_count(true); + + version_ = 0; +} + +void PdmlIgmpProtocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + bool isOk; + OstProto::Gmp *igmp = pbProto->MutableExtension(OstProto::igmp); + QString valueHexStr = attributes.value("value").toString(); + + if (name == "igmp.version") + { + version_ = attributes.value("show").toString().toUInt(&isOk); + } + else if (name == "igmp.type") + { + uint type = valueHexStr.toUInt(&isOk, kBaseHex); + if (type == kIgmpQuery) + { + switch(version_) + { + case 1: type = kIgmpV1Query; break; + case 2: type = kIgmpV2Query; break; + case 3: type = kIgmpV3Query; break; + } + } + igmp->set_type(type); + } + else if (name == "igmp.record_type") + { + OstProto::Gmp::GroupRecord *rec = igmp->add_group_records(); + rec->set_type(OstProto::Gmp::GroupRecord::RecordType( + valueHexStr.toUInt(&isOk, kBaseHex))); + rec->set_is_override_source_count(true); + rec->set_is_override_aux_data_length(true); + } + else if (name == "igmp.aux_data_len") + { + igmp->mutable_group_records(igmp->group_records_size() - 1)-> + set_aux_data_length(valueHexStr.toUInt(&isOk, kBaseHex)); + } + else if (name == "igmp.num_src") + { + if (igmp->group_record_count()) + igmp->mutable_group_records(igmp->group_records_size() - 1)-> + set_source_count(valueHexStr.toUInt(&isOk, kBaseHex)); + else + igmp->set_source_count(valueHexStr.toUInt(&isOk, kBaseHex)); + } + else if (name == "igmp.maddr") + { + if (igmp->group_record_count()) + igmp->mutable_group_records(igmp->group_records_size() - 1)-> + mutable_group_address()->set_v4( + valueHexStr.toUInt(&isOk, kBaseHex)); + else + igmp->mutable_group_address()->set_v4( + valueHexStr.toUInt(&isOk, kBaseHex)); + } + else if (name == "igmp.saddr") + { + if (igmp->group_record_count()) + igmp->mutable_group_records(igmp->group_records_size() - 1)-> + add_sources()->set_v4(valueHexStr.toUInt(&isOk, kBaseHex)); + else + igmp->add_sources()->set_v4(valueHexStr.toUInt(&isOk, kBaseHex)); + } + else if (name == "igmp.aux_data") + { + QByteArray ba = QByteArray::fromHex( + attributes.value("value").toString().toUtf8()); + igmp->mutable_group_records(igmp->group_records_size() - 1)-> + set_aux_data(ba.constData(), ba.size()); + } +} + +void PdmlIgmpProtocol::postProtocolHandler(OstProto::Protocol* /*pbProto*/, + OstProto::Stream *stream) +{ + // version is 0 for IGMP like protocols such as RGMP which we don't + // support currently + if (version_ == 0) + stream->mutable_protocol()->RemoveLast(); +} + diff --git a/common/igmppdml.h b/common/igmppdml.h new file mode 100644 index 0000000..4b553a7 --- /dev/null +++ b/common/igmppdml.h @@ -0,0 +1,49 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IGMP_PDML_H +#define _IGMP_PDML_H + +#include "pdmlprotocol.h" + +class PdmlIgmpProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); +protected: + PdmlIgmpProtocol(); +private: + static const uint kIgmpQuery = 0x11; + static const uint kIgmpV1Query = 0x11; + static const uint kIgmpV2Query = 0xFF11; + static const uint kIgmpV3Query = 0xFE11; + + uint version_; +}; + +#endif diff --git a/common/intcombobox.h b/common/intcombobox.h new file mode 100644 index 0000000..f52bdef --- /dev/null +++ b/common/intcombobox.h @@ -0,0 +1,69 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef __INT_COMBO_BOX +#define __INT_COMBO_BOX + +#include + +class IntComboBox : public QComboBox +{ +public: + IntComboBox(QWidget *parent = 0) + : QComboBox(parent) + { + valueMask_ = 0xFFFFFFFF; + setEditable(true); + } + void addItem(int value, const QString &text) + { + QComboBox::addItem( + QString("%1 - %2").arg(value & valueMask_).arg(text), + value); + } + int currentValue() + { + bool isOk; + int index = findText(currentText()); + if (index >= 0) + return itemData(index).toInt(); + else + return currentText().toInt(&isOk, 0); + } + void setValue(int value) + { + int index = findData(value); + if (index >= 0) + setCurrentIndex(index); + else + setEditText(QString().setNum(value)); + } + uint valueMask() + { + return valueMask_; + } + void setValueMask(uint mask) + { + valueMask_ = mask; + } +private: + uint valueMask_; +}; + +#endif diff --git a/common/ip4.cpp b/common/ip4.cpp new file mode 100644 index 0000000..1c1557b --- /dev/null +++ b/common/ip4.cpp @@ -0,0 +1,846 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "ip4.h" + +#include + +Ip4Protocol::Ip4Protocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +Ip4Protocol::~Ip4Protocol() +{ +} + +AbstractProtocol* Ip4Protocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new Ip4Protocol(stream, parent); +} + +quint32 Ip4Protocol::protocolNumber() const +{ + return OstProto::Protocol::kIp4FieldNumber; +} + +void Ip4Protocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::ip4)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void Ip4Protocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::ip4)) + data.MergeFrom(protocol.GetExtension(OstProto::ip4)); +} + +QString Ip4Protocol::name() const +{ + return QString("Internet Protocol ver 4"); +} + +QString Ip4Protocol::shortName() const +{ + return QString("IPv4"); +} + +AbstractProtocol::ProtocolIdType Ip4Protocol::protocolIdType() const +{ + return ProtocolIdIp; +} + +quint32 Ip4Protocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdLlc: return 0x060603; + case ProtocolIdEth: return 0x0800; + case ProtocolIdIp: return 0x04; + default:break; + } + + return AbstractProtocol::protocolId(type); +} + +int Ip4Protocol::fieldCount() const +{ + return ip4_fieldCount; +} + +AbstractProtocol::FieldFlags Ip4Protocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case ip4_ver: + case ip4_hdrLen: + case ip4_tos: + case ip4_totLen: + case ip4_id: + case ip4_flags: + case ip4_fragOfs: + case ip4_ttl: + case ip4_proto: + break; + + case ip4_cksum: + flags |= CksumField; + break; + + case ip4_srcAddr: + case ip4_dstAddr: + break; + + case ip4_isOverrideVer: + case ip4_isOverrideHdrLen: + case ip4_isOverrideTotLen: + case ip4_isOverrideProto: + case ip4_isOverrideCksum: + case ip4_srcAddrMode: + case ip4_srcAddrCount: + case ip4_srcAddrMask: + case ip4_dstAddrMode: + case ip4_dstAddrCount: + case ip4_dstAddrMask: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + break; + } + + return flags; +} + +QVariant Ip4Protocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case ip4_ver: + { + int ver; + + ver = data.is_override_ver() ? (data.ver_hdrlen() >> 4) & 0x0F : 4; + + switch(attrib) + { + case FieldName: + return QString("Version"); + case FieldValue: + return ver; + case FieldTextValue: + return QString("%1").arg(ver, 1, BASE_HEX, QChar('0')); + case FieldFrameValue: + return QByteArray(1, (char) ver); + case FieldBitSize: + return 4; + default: + break; + } + break; + } + case ip4_hdrLen: + { + int hdrlen; + + hdrlen = data.is_override_hdrlen() ? data.ver_hdrlen() & 0x0F : 5; + + switch(attrib) + { + case FieldName: + return QString("Header Length"); + case FieldValue: + return hdrlen; + case FieldTextValue: + return QString("%1").arg(hdrlen, 1, BASE_HEX, QChar('0')); + case FieldFrameValue: + return QByteArray(1, (char) hdrlen); + case FieldBitSize: + return 4; + default: + break; + } + break; + } + case ip4_tos: + switch(attrib) + { + case FieldName: + return QString("TOS/DSCP"); + case FieldValue: + return data.tos(); + case FieldFrameValue: + return QByteArray(1, (char) data.tos()); + case FieldTextValue: + return QString("0x%1"). + arg(data.tos(), 2, BASE_HEX, QChar('0'));; + default: + break; + } + break; + case ip4_totLen: + { + switch(attrib) + { + case FieldName: + return QString("Total Length"); + case FieldValue: + { + int totlen; + totlen = data.is_override_totlen() ? data.totlen() : + (protocolFramePayloadSize(streamIndex) + 20); + return totlen; + } + case FieldFrameValue: + { + QByteArray fv; + int totlen; + totlen = data.is_override_totlen() ? data.totlen() : + (protocolFramePayloadSize(streamIndex) + 20); + fv.resize(2); + qToBigEndian((quint16) totlen, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + { + int totlen; + totlen = data.is_override_totlen() ? data.totlen() : + (protocolFramePayloadSize(streamIndex) + 20); + return QString("%1").arg(totlen); + } + case FieldBitSize: + return 16; + default: + break; + } + break; + } + case ip4_id: + switch(attrib) + { + case FieldName: + return QString("Identification"); + case FieldValue: + return data.id(); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.id(), (uchar*)fv.data()); + return fv; + } + case FieldTextValue: + return QString("0x%1"). + arg(data.id(), 2, BASE_HEX, QChar('0'));; + default: + break; + } + break; + case ip4_flags: + switch(attrib) + { + case FieldName: + return QString("Flags"); + case FieldValue: + return data.flags(); + case FieldFrameValue: + return QByteArray(1, (char) data.flags()); + case FieldTextValue: + { + QString s; + s.append("Unused:"); + s.append(data.flags() & IP_FLAG_UNUSED ? "1" : "0"); + s.append(" Don't Fragment:"); + s.append(data.flags() & IP_FLAG_DF ? "1" : "0"); + s.append(" More Fragments:"); + s.append(data.flags() & IP_FLAG_MF ? "1" : "0"); + return s; + } + case FieldBitSize: + return 3; + default: + break; + } + break; + case ip4_fragOfs: + switch(attrib) + { + case FieldName: + return QString("Fragment Offset"); + case FieldValue: + return data.frag_ofs(); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) (data.frag_ofs()), + (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QString("%1").arg(data.frag_ofs()*8); + case FieldBitSize: + return 13; + default: + break; + } + break; + case ip4_ttl: + switch(attrib) + { + case FieldName: + return QString("Time to Live"); + case FieldValue: + return data.ttl(); + case FieldFrameValue: + return QByteArray(1, (char)data.ttl()); + case FieldTextValue: + return QString("%1").arg(data.ttl()); + default: + break; + } + break; + case ip4_proto: + { + switch(attrib) + { + case FieldName: + return QString("Protocol"); + case FieldValue: + { + unsigned char id = data.is_override_proto() ? + data.proto() : payloadProtocolId(ProtocolIdIp); + return id; + } + case FieldFrameValue: + { + unsigned char id = data.is_override_proto() ? + data.proto() : payloadProtocolId(ProtocolIdIp); + return QByteArray(1, (char) id); + } + case FieldTextValue: + { + unsigned char id = data.is_override_proto() ? + data.proto() : payloadProtocolId(ProtocolIdIp); + return QString("0x%1"). + arg(id, 2, BASE_HEX, QChar('0')); + } + default: + break; + } + break; + } + case ip4_cksum: + { + switch(attrib) + { + case FieldName: + return QString("Header Checksum"); + case FieldValue: + { + quint16 cksum; + + if (data.is_override_cksum()) + cksum = data.cksum(); + else + cksum = protocolFrameCksum(streamIndex, CksumIp); + return cksum; + } + case FieldFrameValue: + { + QByteArray fv; + quint16 cksum; + + if (data.is_override_cksum()) + cksum = data.cksum(); + else + cksum = protocolFrameCksum(streamIndex, CksumIp); + + fv.resize(2); + qToBigEndian((quint16) cksum, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + { + quint16 cksum; + + if (data.is_override_cksum()) + cksum = data.cksum(); + else + cksum = protocolFrameCksum(streamIndex, CksumIp); + return QString("0x%1"). + arg(cksum, 4, BASE_HEX, QChar('0'));; + } + case FieldBitSize: + return 16; + default: + break; + } + break; + } + case ip4_srcAddr: + { + int u; + quint32 subnet, host, srcIp = 0; + + switch(data.src_ip_mode()) + { + case OstProto::Ip4::e_im_fixed: + srcIp = data.src_ip(); + break; + case OstProto::Ip4::e_im_inc_host: + u = streamIndex % data.src_ip_count(); + subnet = data.src_ip() & data.src_ip_mask(); + host = (((data.src_ip() & ~data.src_ip_mask()) + u) & + ~data.src_ip_mask()); + srcIp = subnet | host; + break; + case OstProto::Ip4::e_im_dec_host: + u = streamIndex % data.src_ip_count(); + subnet = data.src_ip() & data.src_ip_mask(); + host = (((data.src_ip() & ~data.src_ip_mask()) - u) & + ~data.src_ip_mask()); + srcIp = subnet | host; + break; + case OstProto::Ip4::e_im_random_host: + subnet = data.src_ip() & data.src_ip_mask(); + host = (qrand() & ~data.src_ip_mask()); + srcIp = subnet | host; + break; + default: + qWarning("Unhandled src_ip_mode = %d", data.src_ip_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Source"); + case FieldValue: + return srcIp; + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian(srcIp, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QHostAddress(srcIp).toString(); + default: + break; + } + break; + } + case ip4_dstAddr: + { + int u; + quint32 subnet, host, dstIp = 0; + + switch(data.dst_ip_mode()) + { + case OstProto::Ip4::e_im_fixed: + dstIp = data.dst_ip(); + break; + case OstProto::Ip4::e_im_inc_host: + u = streamIndex % data.dst_ip_count(); + subnet = data.dst_ip() & data.dst_ip_mask(); + host = (((data.dst_ip() & ~data.dst_ip_mask()) + u) & + ~data.dst_ip_mask()); + dstIp = subnet | host; + break; + case OstProto::Ip4::e_im_dec_host: + u = streamIndex % data.dst_ip_count(); + subnet = data.dst_ip() & data.dst_ip_mask(); + host = (((data.dst_ip() & ~data.dst_ip_mask()) - u) & + ~data.dst_ip_mask()); + dstIp = subnet | host; + break; + case OstProto::Ip4::e_im_random_host: + subnet = data.dst_ip() & data.dst_ip_mask(); + host = (qrand() & ~data.dst_ip_mask()); + dstIp = subnet | host; + break; + default: + qWarning("Unhandled dst_ip_mode = %d", data.dst_ip_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Destination"); + case FieldValue: + return dstIp; + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian((quint32) dstIp, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QHostAddress(dstIp).toString(); + default: + break; + } + break; + } + + // Meta fields + case ip4_isOverrideVer: + switch(attrib) + { + case FieldValue: return data.is_override_ver(); + default: break; + } + break; + case ip4_isOverrideHdrLen: + switch(attrib) + { + case FieldValue: return data.is_override_hdrlen(); + default: break; + } + break; + case ip4_isOverrideTotLen: + switch(attrib) + { + case FieldValue: return data.is_override_totlen(); + default: break; + } + break; + case ip4_isOverrideProto: + switch(attrib) + { + case FieldValue: return data.is_override_proto(); + default: break; + } + break; + case ip4_isOverrideCksum: + switch(attrib) + { + case FieldValue: return data.is_override_cksum(); + default: break; + } + break; + + case ip4_srcAddrMode: + switch(attrib) + { + case FieldValue: return data.src_ip_mode(); + default: break; + } + break; + case ip4_srcAddrCount: + switch(attrib) + { + case FieldValue: return data.src_ip_count(); + default: break; + } + break; + case ip4_srcAddrMask: + switch(attrib) + { + case FieldValue: return data.src_ip_mask(); + default: break; + } + break; + + case ip4_dstAddrMode: + switch(attrib) + { + case FieldValue: return data.dst_ip_mode(); + default: break; + } + break; + case ip4_dstAddrCount: + switch(attrib) + { + case FieldValue: return data.dst_ip_count(); + default: break; + } + break; + case ip4_dstAddrMask: + switch(attrib) + { + case FieldValue: return data.dst_ip_mask(); + default: break; + } + break; + default: + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool Ip4Protocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case ip4_ver: + { + uint version = value.toUInt(&isOk); + if (isOk) + data.set_ver_hdrlen( + ((version & 0xF) << 4) + | (data.ver_hdrlen() & 0x0F)); + break; + } + case ip4_hdrLen: + { + uint hdrLen = value.toUInt(&isOk); + if (isOk) + data.set_ver_hdrlen( + (data.ver_hdrlen() & 0xF0) + | (hdrLen & 0x0F)); + break; + } + case ip4_tos: + { + uint tos = value.toUInt(&isOk); + if (isOk) + data.set_tos(tos); + break; + } + case ip4_totLen: + { + uint totLen = value.toUInt(&isOk); + if (isOk) + data.set_totlen(totLen); + break; + } + case ip4_id: + { + uint id = value.toUInt(&isOk); + if (isOk) + data.set_id(id); + break; + } + case ip4_flags: + { + uint flags = value.toUInt(&isOk); + if (isOk) + data.set_flags(flags); + break; + } + case ip4_fragOfs: + { + uint fragOfs = value.toUInt(&isOk); + if (isOk) + data.set_frag_ofs(fragOfs); + break; + } + case ip4_ttl: + { + uint ttl = value.toUInt(&isOk); + if (isOk) + data.set_ttl(ttl); + break; + } + case ip4_proto: + { + uint proto = value.toUInt(&isOk); + if (isOk) + data.set_proto(proto); + break; + } + case ip4_cksum: + { + uint cksum = value.toUInt(&isOk); + if (isOk) + data.set_cksum(cksum); + break; + } + case ip4_srcAddr: + { + quint32 srcIp = value.toUInt(&isOk); + if (isOk) + data.set_src_ip(srcIp); + break; + } + case ip4_dstAddr: + { + quint32 dstIp = value.toUInt(&isOk); + if (isOk) + data.set_dst_ip(dstIp); + break; + } + + // Meta-fields + case ip4_isOverrideVer: + { + bool ovr = value.toBool(); + data.set_is_override_ver(ovr); + isOk = true; + break; + } + case ip4_isOverrideHdrLen: + { + bool ovr = value.toBool(); + data.set_is_override_hdrlen(ovr); + isOk = true; + break; + } + case ip4_isOverrideTotLen: + { + bool ovr = value.toBool(); + data.set_is_override_totlen(ovr); + isOk = true; + break; + } + case ip4_isOverrideProto: + { + bool ovr = value.toBool(); + data.set_is_override_proto(ovr); + isOk = true; + break; + } + case ip4_isOverrideCksum: + { + bool ovr = value.toBool(); + data.set_is_override_cksum(ovr); + isOk = true; + break; + } + + case ip4_srcAddrMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.IpAddrMode_IsValid(mode)) + data.set_src_ip_mode(OstProto::Ip4::IpAddrMode(mode)); + else + isOk = false; + break; + } + case ip4_srcAddrCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_src_ip_count(count); + break; + } + case ip4_srcAddrMask: + { + quint32 mask = value.toUInt(&isOk); + if (isOk) + data.set_src_ip_mask(mask); + break; + } + + case ip4_dstAddrMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.IpAddrMode_IsValid(mode)) + data.set_dst_ip_mode(OstProto::Ip4::IpAddrMode(mode)); + else + isOk = false; + break; + } + case ip4_dstAddrCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_dst_ip_count(count); + break; + } + case ip4_dstAddrMask: + { + quint32 mask = value.toUInt(&isOk); + if (isOk) + data.set_dst_ip_mask(mask); + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +bool Ip4Protocol::isProtocolFrameValueVariable() const +{ + if ((data.src_ip_mode() != OstProto::Ip4::e_im_fixed) + || (data.dst_ip_mode() != OstProto::Ip4::e_im_fixed)) + return true; + else + return false; +} + +int Ip4Protocol::protocolFrameVariableCount() const +{ + int count = 1; + + if (data.src_ip_mode() != OstProto::Ip4::e_im_fixed) + count = AbstractProtocol::lcm(count, data.src_ip_count()); + + if (data.dst_ip_mode() != OstProto::Ip4::e_im_fixed) + count = AbstractProtocol::lcm(count, data.dst_ip_count()); + + return count; +} + +quint32 Ip4Protocol::protocolFrameCksum(int streamIndex, + CksumType cksumType) const +{ + switch (cksumType) + { + case CksumIpPseudo: + { + quint32 sum; + + sum = fieldData(ip4_srcAddr, FieldValue, streamIndex).toUInt() >> 16; + sum += fieldData(ip4_srcAddr, FieldValue, streamIndex).toUInt() & 0xFFFF; + sum += fieldData(ip4_dstAddr, FieldValue, streamIndex).toUInt() >> 16; + sum += fieldData(ip4_dstAddr, FieldValue, streamIndex).toUInt() & 0xFFFF; + + sum += fieldData(ip4_proto, FieldValue, streamIndex).toUInt() & 0x00FF; + sum += (fieldData(ip4_totLen, FieldValue, streamIndex).toUInt() & 0xFFFF) - 20; + + while(sum>>16) + sum = (sum & 0xFFFF) + (sum >> 16); + + // Above calculation done assuming 'big endian' + // - so convert to host order + //return qFromBigEndian(sum); + return ~sum; + } + default: + break; + } + + return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); +} diff --git a/common/ip4.h b/common/ip4.h new file mode 100644 index 0000000..1ea8128 --- /dev/null +++ b/common/ip4.h @@ -0,0 +1,99 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IPV4_H +#define _IPV4_H + +#include "abstractprotocol.h" +#include "ip4.pb.h" + +#define IP_FLAG_MF 0x1 +#define IP_FLAG_DF 0x2 +#define IP_FLAG_UNUSED 0x4 + +class Ip4Protocol : public AbstractProtocol +{ +public: + enum ip4field + { + ip4_ver = 0, + ip4_hdrLen, + ip4_tos, + ip4_totLen, + ip4_id, + ip4_flags, + ip4_fragOfs, + ip4_ttl, + ip4_proto, + ip4_cksum, + ip4_srcAddr, + ip4_dstAddr, + + // Meta-fields + ip4_isOverrideVer, + ip4_isOverrideHdrLen, + ip4_isOverrideTotLen, + ip4_isOverrideProto, + ip4_isOverrideCksum, + + ip4_srcAddrMode, + ip4_srcAddrCount, + ip4_srcAddrMask, + + ip4_dstAddrMode, + ip4_dstAddrCount, + ip4_dstAddrMask, + + ip4_fieldCount + }; + + Ip4Protocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~Ip4Protocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + virtual ProtocolIdType protocolIdType() const; + virtual quint32 protocolId(ProtocolIdType type) const; + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual bool isProtocolFrameValueVariable() const; + virtual int protocolFrameVariableCount() const; + + virtual quint32 protocolFrameCksum(int streamIndex = 0, + CksumType cksumType = CksumIp) const; + +private: + OstProto::Ip4 data; +}; + + +#endif diff --git a/common/ip4.proto b/common/ip4.proto new file mode 100644 index 0000000..be7391d --- /dev/null +++ b/common/ip4.proto @@ -0,0 +1,66 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; +// IPv4 +message Ip4 { + + enum IpAddrMode { + e_im_fixed = 0; + e_im_inc_host = 1; + e_im_dec_host = 2; + e_im_random_host = 3; + } + + optional bool is_override_ver = 1; + optional bool is_override_hdrlen = 2; + optional bool is_override_totlen = 3; + optional bool is_override_proto = 30; + optional bool is_override_cksum = 4; + + optional uint32 ver_hdrlen = 5 [default = 0x45]; + optional uint32 tos = 6; + optional uint32 totlen = 7; + optional uint32 id = 8 [default = 1234]; + optional uint32 flags = 9; + optional uint32 frag_ofs = 10; + optional uint32 ttl = 11 [default = 127]; + optional uint32 proto = 12; + optional uint32 cksum = 13; + + // Source IP + optional fixed32 src_ip = 14; + optional IpAddrMode src_ip_mode = 15 [default = e_im_fixed]; + optional uint32 src_ip_count = 16 [default = 16]; + optional fixed32 src_ip_mask = 17 [default = 0xFFFFFF00]; + + // Destination IP + optional fixed32 dst_ip = 18; + optional IpAddrMode dst_ip_mode = 19 [default = e_im_fixed]; + optional uint32 dst_ip_count = 20 [default = 16]; + optional fixed32 dst_ip_mask = 21 [default = 0xFFFFFF00]; + + //! \todo (LOW) IPv4 Options +} + +extend Protocol { + optional Ip4 ip4 = 301; +} diff --git a/common/ip4.ui b/common/ip4.ui new file mode 100644 index 0000000..3e98d7c --- /dev/null +++ b/common/ip4.ui @@ -0,0 +1,516 @@ + + ip4 + + + + 0 + 0 + 507 + 308 + + + + Form + + + + + + + + Override Version + + + + + + + false + + + + + + + + + + Override Header +Length (x4) + + + + + + + false + + + + + + + + + + TOS/DSCP + + + + + + + >HH; + + + + + + + + + + Override Length + + + + + + + false + + + + + + + Identification + + + + + + + >HH HH; + + + + + + + + + + + Fragment Offset (x8) + + + + + + + + + + Don't Fragment + + + + + + + More Fragments + + + + + + + Time To Live (TTL) + + + + + + + + + + + + + + false + + + >HH; + + + + + + + + + + Override Checksum + + + + + + + false + + + >HH HH; + + + + + + + Override Protocol + + + + + + + + + + + + false + + + + + + Qt::Horizontal + + + + 101 + 20 + + + + + + + + Mode + + + + + + + Count + + + + + + + Mask + + + + + + + Source + + + + + + + 009.009.009.009; + + + ... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Fixed + + + + + Increment Host + + + + + Decrement Host + + + + + Random Host + + + + + + + + false + + + + + + + + + + false + + + 009.009.009.009; + + + ... + + + + + + + Destination + + + + + + + 000.000.000.000; + + + ... + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Fixed + + + + + Increment Host + + + + + Decrement Host + + + + + Random Host + + + + + + + + false + + + + + + + + + + false + + + 009.009.009.009; + + + ... + + + + + + + + + + + + Options + + + + + + + false + + + TODO + + + + + + + false + + + ... + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + cbIpVersionOverride + leIpVersion + cbIpHdrLenOverride + leIpHdrLen + leIpTos + cbIpLengthOverride + leIpLength + leIpId + leIpFragOfs + cbIpFlagsDf + cbIpFlagsMf + leIpTtl + cbIpProtocolOverride + leIpProto + cbIpCksumOverride + leIpCksum + leIpSrcAddr + cmbIpSrcAddrMode + leIpSrcAddrCount + leIpSrcAddrMask + leIpDstAddr + cmbIpDstAddrMode + leIpDstAddrCount + leIpDstAddrMask + leIpOptions + tbIpOptionsEdit + + + + + cbIpVersionOverride + toggled(bool) + leIpVersion + setEnabled(bool) + + + 108 + 11 + + + 195 + 11 + + + + + cbIpHdrLenOverride + toggled(bool) + leIpHdrLen + setEnabled(bool) + + + 113 + 67 + + + 166 + 43 + + + + + cbIpLengthOverride + toggled(bool) + leIpLength + setEnabled(bool) + + + 89 + 118 + + + 236 + 119 + + + + + cbIpCksumOverride + toggled(bool) + leIpCksum + setEnabled(bool) + + + 387 + 140 + + + 406 + 122 + + + + + cbIpProtocolOverride + toggled(bool) + leIpProto + setEnabled(bool) + + + 363 + 95 + + + 398 + 94 + + + + + diff --git a/common/ip4config.cpp b/common/ip4config.cpp new file mode 100644 index 0000000..261da7a --- /dev/null +++ b/common/ip4config.cpp @@ -0,0 +1,297 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "ip4config.h" +#include "ip4.h" + +#include + +Ip4ConfigForm::Ip4ConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); + + leIpVersion->setValidator(new QIntValidator(0, 15, this)); + + connect(cmbIpSrcAddrMode, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_cmbIpSrcAddrMode_currentIndexChanged(int))); + connect(cmbIpDstAddrMode, SIGNAL(currentIndexChanged(int)), + this, SLOT(on_cmbIpDstAddrMode_currentIndexChanged(int))); +} + +Ip4ConfigForm::~Ip4ConfigForm() +{ +} + + +Ip4ConfigForm* Ip4ConfigForm::createInstance() +{ + return new Ip4ConfigForm; +} + +void Ip4ConfigForm::loadWidget(AbstractProtocol *proto) +{ + cbIpVersionOverride->setChecked( + proto->fieldData( + Ip4Protocol::ip4_isOverrideVer, + AbstractProtocol::FieldValue + ).toBool()); + leIpVersion->setText( + proto->fieldData( + Ip4Protocol::ip4_ver, + AbstractProtocol::FieldValue + ).toString()); + + cbIpHdrLenOverride->setChecked( + proto->fieldData( + Ip4Protocol::ip4_isOverrideHdrLen, + AbstractProtocol::FieldValue + ).toBool()); + leIpHdrLen->setText( + proto->fieldData( + Ip4Protocol::ip4_hdrLen, + AbstractProtocol::FieldValue + ).toString()); + + leIpTos->setText(uintToHexStr( + proto->fieldData( + Ip4Protocol::ip4_tos, + AbstractProtocol::FieldValue + ).toUInt(), 1)); + + cbIpLengthOverride->setChecked( + proto->fieldData( + Ip4Protocol::ip4_isOverrideTotLen, + AbstractProtocol::FieldValue + ).toBool()); + leIpLength->setText( + proto->fieldData( + Ip4Protocol::ip4_totLen, + AbstractProtocol::FieldValue + ).toString()); + + leIpId->setText(uintToHexStr( + proto->fieldData( + Ip4Protocol::ip4_id, + AbstractProtocol::FieldValue + ).toUInt(), 2)); + leIpFragOfs->setText( + proto->fieldData( + Ip4Protocol::ip4_fragOfs, + AbstractProtocol::FieldValue + ).toString()); + cbIpFlagsDf->setChecked(( + proto->fieldData( + Ip4Protocol::ip4_flags, + AbstractProtocol::FieldValue + ).toUInt() & IP_FLAG_DF) > 0); + cbIpFlagsMf->setChecked(( + proto->fieldData( + Ip4Protocol::ip4_flags, + AbstractProtocol::FieldValue + ).toUInt() & IP_FLAG_MF) > 0); + + leIpTtl->setText( + proto->fieldData( + Ip4Protocol::ip4_ttl, + AbstractProtocol::FieldValue + ).toString()); + + cbIpProtocolOverride->setChecked( + proto->fieldData( + Ip4Protocol::ip4_isOverrideProto, + AbstractProtocol::FieldValue + ).toBool()); + leIpProto->setText(uintToHexStr( + proto->fieldData( + Ip4Protocol::ip4_proto, + AbstractProtocol::FieldValue + ).toUInt(), 1)); + + cbIpCksumOverride->setChecked( + proto->fieldData( + Ip4Protocol::ip4_isOverrideCksum, + AbstractProtocol::FieldValue + ).toBool()); + leIpCksum->setText(uintToHexStr( + proto->fieldData( + Ip4Protocol::ip4_cksum, + AbstractProtocol::FieldValue + ).toUInt(), 2)); + + leIpSrcAddr->setText(QHostAddress( + proto->fieldData( + Ip4Protocol::ip4_srcAddr, + AbstractProtocol::FieldValue + ).toUInt()).toString()); + cmbIpSrcAddrMode->setCurrentIndex( + proto->fieldData( + Ip4Protocol::ip4_srcAddrMode, + AbstractProtocol::FieldValue + ).toUInt()); + leIpSrcAddrCount->setText( + proto->fieldData( + Ip4Protocol::ip4_srcAddrCount, + AbstractProtocol::FieldValue + ).toString()); + leIpSrcAddrMask->setText(QHostAddress( + proto->fieldData( + Ip4Protocol::ip4_srcAddrMask, + AbstractProtocol::FieldValue + ).toUInt()).toString()); + + leIpDstAddr->setText(QHostAddress( + proto->fieldData( + Ip4Protocol::ip4_dstAddr, + AbstractProtocol::FieldValue + ).toUInt()).toString()); + cmbIpDstAddrMode->setCurrentIndex( + proto->fieldData( + Ip4Protocol::ip4_dstAddrMode, + AbstractProtocol::FieldValue + ).toUInt()); + leIpDstAddrCount->setText( + proto->fieldData( + Ip4Protocol::ip4_dstAddrCount, + AbstractProtocol::FieldValue + ).toString()); + leIpDstAddrMask->setText(QHostAddress( + proto->fieldData( + Ip4Protocol::ip4_dstAddrMask, + AbstractProtocol::FieldValue + ).toUInt()).toString()); +} + +void Ip4ConfigForm::storeWidget(AbstractProtocol *proto) +{ + uint ff = 0; + + proto->setFieldData( + Ip4Protocol::ip4_isOverrideVer, + cbIpVersionOverride->isChecked()); + proto->setFieldData( + Ip4Protocol::ip4_ver, + leIpVersion->text()); + + proto->setFieldData( + Ip4Protocol::ip4_isOverrideHdrLen, + cbIpHdrLenOverride->isChecked()); + proto->setFieldData( + Ip4Protocol::ip4_hdrLen, + leIpHdrLen->text()); + + proto->setFieldData( + Ip4Protocol::ip4_tos, + hexStrToUInt(leIpTos->text())); + + proto->setFieldData( + Ip4Protocol::ip4_totLen, + leIpLength->text()); + proto->setFieldData( + Ip4Protocol::ip4_isOverrideTotLen, + cbIpLengthOverride->isChecked()); + + proto->setFieldData( + Ip4Protocol::ip4_id, + hexStrToUInt(leIpId->text())); + proto->setFieldData( + Ip4Protocol::ip4_fragOfs, + leIpFragOfs->text()); + + if (cbIpFlagsDf->isChecked()) ff |= IP_FLAG_DF; + if (cbIpFlagsMf->isChecked()) ff |= IP_FLAG_MF; + proto->setFieldData( + Ip4Protocol::ip4_flags, + ff); + + proto->setFieldData( + Ip4Protocol::ip4_ttl, + leIpTtl->text()); + + proto->setFieldData( + Ip4Protocol::ip4_isOverrideProto, + cbIpProtocolOverride->isChecked()); + proto->setFieldData( + Ip4Protocol::ip4_proto, + hexStrToUInt(leIpProto->text())); + + proto->setFieldData( + Ip4Protocol::ip4_isOverrideCksum, + cbIpCksumOverride->isChecked()); + proto->setFieldData( + Ip4Protocol::ip4_cksum, + hexStrToUInt(leIpCksum->text())); + + proto->setFieldData( + Ip4Protocol::ip4_srcAddr, + QHostAddress(leIpSrcAddr->text()).toIPv4Address()); + proto->setFieldData( + Ip4Protocol::ip4_srcAddrMode, + (OstProto::Ip4_IpAddrMode)cmbIpSrcAddrMode->currentIndex()); + proto->setFieldData( + Ip4Protocol::ip4_srcAddrCount, + leIpSrcAddrCount->text()); + proto->setFieldData( + Ip4Protocol::ip4_srcAddrMask, + QHostAddress(leIpSrcAddrMask->text()).toIPv4Address()); + + proto->setFieldData( + Ip4Protocol::ip4_dstAddr, + QHostAddress(leIpDstAddr->text()).toIPv4Address()); + proto->setFieldData( + Ip4Protocol::ip4_dstAddrMode, + (OstProto::Ip4_IpAddrMode)cmbIpDstAddrMode->currentIndex()); + proto->setFieldData( + Ip4Protocol::ip4_dstAddrCount, + leIpDstAddrCount->text()); + proto->setFieldData( + Ip4Protocol::ip4_dstAddrMask, + QHostAddress(leIpDstAddrMask->text()).toIPv4Address()); +} + +/* + * Slots + */ +void Ip4ConfigForm::on_cmbIpSrcAddrMode_currentIndexChanged(int index) +{ + if (index == OstProto::Ip4::e_im_fixed) + { + leIpSrcAddrCount->setDisabled(true); + leIpSrcAddrMask->setDisabled(true); + } + else + { + leIpSrcAddrCount->setEnabled(true); + leIpSrcAddrMask->setEnabled(true); + } +} + +void Ip4ConfigForm::on_cmbIpDstAddrMode_currentIndexChanged(int index) +{ + if (index == OstProto::Ip4::e_im_fixed) + { + leIpDstAddrCount->setDisabled(true); + leIpDstAddrMask->setDisabled(true); + } + else + { + leIpDstAddrCount->setEnabled(true); + leIpDstAddrMask->setEnabled(true); + } +} diff --git a/common/ip4config.h b/common/ip4config.h new file mode 100644 index 0000000..6db7b97 --- /dev/null +++ b/common/ip4config.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IPV4_CONFIG_H +#define _IPV4_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_ip4.h" + +class Ip4ConfigForm : + public AbstractProtocolConfigForm, + private Ui::ip4 +{ + Q_OBJECT +public: + Ip4ConfigForm(QWidget *parent = 0); + virtual ~Ip4ConfigForm(); + + static Ip4ConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private slots: + void on_cmbIpSrcAddrMode_currentIndexChanged(int index); + void on_cmbIpDstAddrMode_currentIndexChanged(int index); +}; +#endif diff --git a/common/ip4over4.h b/common/ip4over4.h new file mode 100644 index 0000000..9ca1be7 --- /dev/null +++ b/common/ip4over4.h @@ -0,0 +1,91 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_4_OVER_4_H +#define _IP_4_OVER_4_H + +#include "ip4over4.pb.h" + +#include "comboprotocol.h" +#include "ip4.h" + +typedef ComboProtocol Ip4over4Combo; + +class Ip4over4Protocol : public Ip4over4Combo +{ +public: + Ip4over4Protocol(StreamBase *stream, AbstractProtocol *parent = 0) + : Ip4over4Combo(stream, parent) + { + } + + static Ip4over4Protocol* createInstance(StreamBase *stream, + AbstractProtocol *parent) + { + return new Ip4over4Protocol(stream, parent); + } + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const + { + OstProto::Protocol tempProto; + + protoA->protoDataCopyInto(tempProto); + protocol.MutableExtension(OstProto::ip4over4) + ->MutableExtension(OstProto::ip4_outer) + ->CopyFrom(tempProto.GetExtension(OstProto::ip4)); + + tempProto.Clear(); + + protoB->protoDataCopyInto(tempProto); + protocol.MutableExtension(OstProto::ip4over4) + ->MutableExtension(OstProto::ip4_inner) + ->CopyFrom(tempProto.GetExtension(OstProto::ip4)); + + protocol.mutable_protocol_id()->set_id(protocolNumber()); + } + + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol) + { + if (protocol.protocol_id().id() == protocolNumber() + && protocol.HasExtension(OstProto::ip4over4)) + { + OstProto::Protocol tempProto; + + // NOTE: To use protoX->protoDataCopyFrom() we need to arrange + // so that it sees its own protocolNumber() and its own extension + // in 'protocol' + tempProto.mutable_protocol_id()->set_id(protoA->protocolNumber()); + tempProto.MutableExtension(OstProto::ip4)->CopyFrom( + protocol.GetExtension(OstProto::ip4over4).GetExtension( + OstProto::ip4_outer)); + protoA->protoDataCopyFrom(tempProto); + + tempProto.Clear(); + + tempProto.mutable_protocol_id()->set_id(protoB->protocolNumber()); + tempProto.MutableExtension(OstProto::ip4)->CopyFrom( + protocol.GetExtension(OstProto::ip4over4).GetExtension( + OstProto::ip4_inner)); + protoB->protoDataCopyFrom(tempProto); + } + } +}; + +#endif diff --git a/common/ip4over4.proto b/common/ip4over4.proto new file mode 100644 index 0000000..5a146c3 --- /dev/null +++ b/common/ip4over4.proto @@ -0,0 +1,37 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; +import "ip4.proto"; + +package OstProto; + +// IP 4over4 (also called IPIP) +message Ip4over4 { + extensions 1 to 2; +} + +extend Ip4over4 { + optional Ip4 ip4_outer = 1; + optional Ip4 ip4_inner = 2; +} + +extend Protocol { + optional Ip4over4 ip4over4 = 305; +} diff --git a/common/ip4over4config.h b/common/ip4over4config.h new file mode 100644 index 0000000..328b14e --- /dev/null +++ b/common/ip4over4config.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_4_OVER_4_CONFIG_H +#define _IP_4_OVER_4_CONFIG_H + +#include "comboprotocolconfig.h" +#include "ip4config.h" +#include "ip4.h" + +#include "protocol.pb.h" + +typedef ComboProtocolConfigForm < + OstProto::Protocol::kIp4over4FieldNumber, + Ip4ConfigForm, Ip4ConfigForm, + Ip4Protocol, Ip4Protocol + > Ip4over4ConfigForm; + +#endif diff --git a/common/ip4over6.h b/common/ip4over6.h new file mode 100644 index 0000000..41bcce0 --- /dev/null +++ b/common/ip4over6.h @@ -0,0 +1,30 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_4_OVER_6_H +#define _IP_4_OVER_6_H + +#include "comboprotocol.h" +#include "ip4.h" +#include "ip6.h" + +typedef ComboProtocol Ip4over6Protocol; + +#endif diff --git a/common/ip4over6.proto b/common/ip4over6.proto new file mode 100644 index 0000000..0482045 --- /dev/null +++ b/common/ip4over6.proto @@ -0,0 +1,31 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// IP Tunelling - IP 4over6 +message Ip4over6 { + // Empty since this is a 'combo' protocol +} + +extend Protocol { + optional Ip4over6 ip4over6 = 304; +} diff --git a/common/ip4over6config.h b/common/ip4over6config.h new file mode 100644 index 0000000..b0ccf63 --- /dev/null +++ b/common/ip4over6config.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_4_OVER_6_CONFIG_H +#define _IP_4_OVER_6_CONFIG_H + +#include "comboprotocolconfig.h" +#include "ip4config.h" +#include "ip6config.h" +#include "ip4.h" +#include "ip6.h" + +typedef ComboProtocolConfigForm < + OstProto::Protocol::kIp4over6FieldNumber, + Ip6ConfigForm, Ip4ConfigForm, + Ip6Protocol, Ip4Protocol + > Ip4over6ConfigForm; + +#endif diff --git a/common/ip4pdml.cpp b/common/ip4pdml.cpp new file mode 100644 index 0000000..f355260 --- /dev/null +++ b/common/ip4pdml.cpp @@ -0,0 +1,93 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "ip4pdml.h" + +#include "hexdump.pb.h" +#include "ip4.pb.h" + +PdmlIp4Protocol::PdmlIp4Protocol() +{ + ostProtoId_ = OstProto::Protocol::kIp4FieldNumber; + + fieldMap_.insert("ip.version", OstProto::Ip4::kVerHdrlenFieldNumber); + fieldMap_.insert("ip.dsfield", OstProto::Ip4::kTosFieldNumber); + fieldMap_.insert("ip.len", OstProto::Ip4::kTotlenFieldNumber); + fieldMap_.insert("ip.id", OstProto::Ip4::kIdFieldNumber); + //fieldMap_.insert("ip.flags", OstProto::Ip4::kFlagsFieldNumber); + fieldMap_.insert("ip.frag_offset", OstProto::Ip4::kFragOfsFieldNumber); + fieldMap_.insert("ip.ttl", OstProto::Ip4::kTtlFieldNumber); + fieldMap_.insert("ip.proto", OstProto::Ip4::kProtoFieldNumber); + fieldMap_.insert("ip.checksum", OstProto::Ip4::kCksumFieldNumber); + fieldMap_.insert("ip.src", OstProto::Ip4::kSrcIpFieldNumber); + fieldMap_.insert("ip.dst", OstProto::Ip4::kDstIpFieldNumber); +} + +PdmlProtocol* PdmlIp4Protocol::createInstance() +{ + return new PdmlIp4Protocol(); +} + +void PdmlIp4Protocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + bool isOk; + + if ((name == "ip.options") || + attributes.value("show").toString().startsWith("Options")) + { + options_ = QByteArray::fromHex( + attributes.value("value").toString().toUtf8()); + } + else if (name == "ip.flags") + { + OstProto::Ip4 *ip4 = pbProto->MutableExtension(OstProto::ip4); + + ip4->set_flags(attributes.value("value").toString().toUInt(&isOk, kBaseHex) >> 5); + } +} + +void PdmlIp4Protocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream) +{ + OstProto::Ip4 *ip4 = pbProto->MutableExtension(OstProto::ip4); + + ip4->set_is_override_ver(true); + ip4->set_is_override_hdrlen(true); + ip4->set_is_override_totlen(true); + ip4->set_is_override_proto(true); + ip4->set_is_override_cksum(true); + + if (options_.size()) + { + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kHexDumpFieldNumber); + + OstProto::HexDump *hexDump = proto->MutableExtension(OstProto::hexDump); + + hexDump->mutable_content()->append(options_.constData(), + options_.size()); + hexDump->set_pad_until_end(false); + options_.resize(0); + } +} + diff --git a/common/ip4pdml.h b/common/ip4pdml.h new file mode 100644 index 0000000..64f818d --- /dev/null +++ b/common/ip4pdml.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP4_PDML_H +#define _IP4_PDML_H + +#include "pdmlprotocol.h" + +class PdmlIp4Protocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); +protected: + PdmlIp4Protocol(); +private: + QByteArray options_; +}; + +#endif diff --git a/common/ip6.cpp b/common/ip6.cpp new file mode 100644 index 0000000..8833516 --- /dev/null +++ b/common/ip6.cpp @@ -0,0 +1,813 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "ip6.h" +#include + + +Ip6Protocol::Ip6Protocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +Ip6Protocol::~Ip6Protocol() +{ +} + +AbstractProtocol* Ip6Protocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new Ip6Protocol(stream, parent); +} + +quint32 Ip6Protocol::protocolNumber() const +{ + return OstProto::Protocol::kIp6FieldNumber; +} + +void Ip6Protocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::ip6)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void Ip6Protocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::ip6)) + data.MergeFrom(protocol.GetExtension(OstProto::ip6)); +} + +QString Ip6Protocol::name() const +{ + return QString("Internet Protocol ver 6"); +} + +QString Ip6Protocol::shortName() const +{ + return QString("IPv6"); +} + +AbstractProtocol::ProtocolIdType Ip6Protocol::protocolIdType() const +{ + return ProtocolIdIp; +} + +quint32 Ip6Protocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdEth: return 0x86dd; + case ProtocolIdIp: return 0x29; + default:break; + } + + return AbstractProtocol::protocolId(type); +} + +int Ip6Protocol::fieldCount() const +{ + return ip6_fieldCount; +} + +AbstractProtocol::FieldFlags Ip6Protocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case ip6_version: + case ip6_trafficClass: + case ip6_flowLabel: + case ip6_payloadLength: + case ip6_nextHeader: + case ip6_hopLimit: + case ip6_srcAddress: + case ip6_dstAddress: + break; + + case ip6_isOverrideVersion: + case ip6_isOverridePayloadLength: + case ip6_isOverrideNextHeader: + + case ip6_srcAddrMode: + case ip6_srcAddrCount: + case ip6_srcAddrPrefix: + + case ip6_dstAddrMode: + case ip6_dstAddrCount: + case ip6_dstAddrPrefix: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +QVariant Ip6Protocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case ip6_version: + { + quint8 ver; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_version()) + ver = data.version() & 0xF; + else + ver = 0x6; + break; + default: + ver = 0; // avoid the 'maybe used unitialized' warning + break; + } + switch(attrib) + { + case FieldName: + return QString("Version"); + case FieldValue: + return ver; + case FieldTextValue: + return QString("%1").arg(ver); + case FieldFrameValue: + return QByteArray(1, char(ver)); + case FieldBitSize: + return 4; + default: + break; + } + break; + } + case ip6_trafficClass: + { + switch(attrib) + { + case FieldName: + return QString("Traffic Class"); + case FieldValue: + return data.traffic_class() & 0xFF; + case FieldTextValue: + return QString("%1").arg(data.traffic_class() & 0xFF, + 2, BASE_HEX, QChar('0')); + case FieldFrameValue: + return QByteArray(1, char(data.traffic_class() & 0xFF)); + default: + break; + } + break; + } + case ip6_flowLabel: + { + switch(attrib) + { + case FieldName: + return QString("Flow Label"); + case FieldValue: + return data.flow_label() & 0xFFFFF; + case FieldTextValue: + return QString("%1").arg(data.flow_label() & 0xFFFFF, + 5, BASE_HEX, QChar('0')); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian((quint32) data.flow_label() & 0xFFFFF, + (uchar*) fv.data()); + fv = fv.right(3); + return fv; + } + case FieldBitSize: + return 20; + default: + break; + } + break; + } + case ip6_payloadLength: + { + quint16 len; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_payload_length()) + len = data.payload_length(); + else + len = protocolFramePayloadSize(streamIndex); + break; + default: + len = 0; // avoid the 'maybe used unitialized' warning + break; + } + switch(attrib) + { + case FieldName: + return QString("Payload Length"); + case FieldValue: + return len; + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(len, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QString("%1").arg(len); + case FieldBitSize: + return 16; + default: + break; + } + break; + } + case ip6_nextHeader: + { + quint8 nextHdr; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_next_header()) { + nextHdr = data.next_header(); + } + else { + nextHdr = payloadProtocolId(ProtocolIdIp); + if ((nextHdr == 0) + && next + && (next->protocolIdType() == ProtocolIdNone)) { + nextHdr = 0x3b; // IPv6 No-Next-Header + } + } + break; + default: + nextHdr = 0; // avoid the 'maybe used unitialized' warning + break; + } + switch(attrib) + { + case FieldName: + return QString("Next Header"); + case FieldValue: + return nextHdr; + case FieldTextValue: + return QString("%1").arg(nextHdr, 2, BASE_HEX, QChar('0')); + case FieldFrameValue: + return QByteArray(1, char(nextHdr)); + default: + break; + } + break; + } + case ip6_hopLimit: + { + switch(attrib) + { + case FieldName: + return QString("Hop Limit"); + case FieldValue: + return data.hop_limit() & 0xFF; + case FieldTextValue: + return QString("%1").arg(data.hop_limit() & 0xFF); + case FieldFrameValue: + return QByteArray(1, char(data.hop_limit() & 0xFF)); + default: + break; + } + break; + } + + case ip6_srcAddress: + { + int u, p, q; + quint64 maskHi = 0, maskLo = 0; + quint64 prefixHi, prefixLo; + quint64 hostHi = 0, hostLo = 0; + quint64 srcHi = 0, srcLo = 0; + + switch(data.src_addr_mode()) + { + case OstProto::Ip6::kFixed: + srcHi = data.src_addr_hi(); + srcLo = data.src_addr_lo(); + break; + case OstProto::Ip6::kIncHost: + case OstProto::Ip6::kDecHost: + case OstProto::Ip6::kRandomHost: + u = streamIndex % data.src_addr_count(); + if (data.src_addr_prefix() > 64) { + p = 64; + q = data.src_addr_prefix() - 64; + } else { + p = data.src_addr_prefix(); + q = 0; + } + if (p > 0) + maskHi = ~((quint64(1) << p) - 1); + if (q > 0) + maskLo = ~((quint64(1) << q) - 1); + prefixHi = data.src_addr_hi() & maskHi; + prefixLo = data.src_addr_lo() & maskLo; + if (data.src_addr_mode() == OstProto::Ip6::kIncHost) { + hostHi = ((data.src_addr_hi() & ~maskHi) + u) & ~maskHi; + hostLo = ((data.src_addr_lo() & ~maskLo) + u) & ~maskLo; + } + else if (data.src_addr_mode() == OstProto::Ip6::kDecHost) { + hostHi = ((data.src_addr_hi() & ~maskHi) - u) & ~maskHi; + hostLo = ((data.src_addr_lo() & ~maskLo) - u) & ~maskLo; + } + else if (data.src_addr_mode()==OstProto::Ip6::kRandomHost) { + hostHi = qrand() & ~maskHi; + hostLo = qrand() & ~maskLo; + } + srcHi = prefixHi | hostHi; + srcLo = prefixLo | hostLo; + break; + default: + qWarning("Unhandled src_addr_mode = %d", + data.src_addr_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Source"); + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + { + QByteArray fv; + fv.resize(16); + qToBigEndian(srcHi, (uchar*) fv.data()); + qToBigEndian(srcLo, (uchar*) (fv.data() + 8)); + if (attrib == FieldTextValue) + return QHostAddress((quint8*)fv.constData()).toString(); + else + return fv; + } + default: + break; + } + break; + } + + case ip6_dstAddress: + { + int u, p, q; + quint64 maskHi = 0, maskLo = 0; + quint64 prefixHi, prefixLo; + quint64 hostHi = 0, hostLo = 0; + quint64 dstHi = 0, dstLo = 0; + + switch(data.dst_addr_mode()) + { + case OstProto::Ip6::kFixed: + dstHi = data.dst_addr_hi(); + dstLo = data.dst_addr_lo(); + break; + case OstProto::Ip6::kIncHost: + case OstProto::Ip6::kDecHost: + case OstProto::Ip6::kRandomHost: + u = streamIndex % data.dst_addr_count(); + if (data.dst_addr_prefix() > 64) { + p = 64; + q = data.dst_addr_prefix() - 64; + } else { + p = data.dst_addr_prefix(); + q = 0; + } + if (p > 0) + maskHi = ~((quint64(1) << p) - 1); + if (q > 0) + maskLo = ~((quint64(1) << q) - 1); + prefixHi = data.dst_addr_hi() & maskHi; + prefixLo = data.dst_addr_lo() & maskLo; + if (data.dst_addr_mode() == OstProto::Ip6::kIncHost) { + hostHi = ((data.dst_addr_hi() & ~maskHi) + u) & ~maskHi; + hostLo = ((data.dst_addr_lo() & ~maskLo) + u) & ~maskLo; + } + else if (data.dst_addr_mode() == OstProto::Ip6::kDecHost) { + hostHi = ((data.dst_addr_hi() & ~maskHi) - u) & ~maskHi; + hostLo = ((data.dst_addr_lo() & ~maskLo) - u) & ~maskLo; + } + else if (data.dst_addr_mode()==OstProto::Ip6::kRandomHost) { + hostHi = qrand() & ~maskHi; + hostLo = qrand() & ~maskLo; + } + dstHi = prefixHi | hostHi; + dstLo = prefixLo | hostLo; + break; + default: + qWarning("Unhandled dst_addr_mode = %d", + data.dst_addr_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Destination"); + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + { + QByteArray fv; + fv.resize(16); + qToBigEndian(dstHi, (uchar*) fv.data()); + qToBigEndian(dstLo, (uchar*) (fv.data() + 8)); + if (attrib == FieldTextValue) + return QHostAddress((quint8*)fv.constData()).toString(); + else + return fv; + } + default: + break; + } + break; + } + + // Meta-Fields + case ip6_isOverrideVersion: + { + switch(attrib) + { + case FieldValue: + return data.is_override_version(); + default: + break; + } + break; + } + case ip6_isOverridePayloadLength: + { + switch(attrib) + { + case FieldValue: + return data.is_override_payload_length(); + default: + break; + } + break; + } + case ip6_isOverrideNextHeader: + { + switch(attrib) + { + case FieldValue: + return data.is_override_next_header(); + default: + break; + } + break; + } + + case ip6_srcAddrMode: + { + switch(attrib) + { + case FieldValue: + return data.src_addr_mode(); + default: + break; + } + break; + } + case ip6_srcAddrCount: + { + switch(attrib) + { + case FieldValue: + return data.src_addr_count(); + default: + break; + } + break; + } + case ip6_srcAddrPrefix: + { + switch(attrib) + { + case FieldValue: + return data.src_addr_prefix(); + default: + break; + } + break; + } + + case ip6_dstAddrMode: + { + switch(attrib) + { + case FieldValue: + return data.dst_addr_mode(); + default: + break; + } + break; + } + case ip6_dstAddrCount: + { + switch(attrib) + { + case FieldValue: + return data.dst_addr_count(); + default: + break; + } + break; + } + case ip6_dstAddrPrefix: + { + switch(attrib) + { + case FieldValue: + return data.dst_addr_prefix(); + default: + break; + } + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool Ip6Protocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case ip6_version: + { + uint ver = value.toUInt(&isOk); + if (isOk) + data.set_version(ver & 0xF); + break; + } + case ip6_trafficClass: + { + uint trfClass = value.toUInt(&isOk); + if (isOk) + data.set_traffic_class(trfClass & 0xFF); + break; + } + case ip6_flowLabel: + { + uint fl = value.toUInt(&isOk); + if (isOk) + data.set_flow_label(fl & 0xFFFFF); + break; + } + case ip6_payloadLength: + { + uint len = value.toUInt(&isOk); + if (isOk) + data.set_payload_length(len & 0xFFFF); + break; + } + case ip6_nextHeader: + { + uint ver = value.toUInt(&isOk); + if (isOk) + data.set_next_header(ver & 0xFF); + break; + } + case ip6_hopLimit: + { + uint hl = value.toUInt(&isOk); + if (isOk) + data.set_hop_limit(hl & 0xFF); + break; + } + case ip6_srcAddress: + { + Q_IPV6ADDR addr = QHostAddress(value.toString()).toIPv6Address(); + quint64 x; + + x = (quint64(addr[0]) << 56) + | (quint64(addr[1]) << 48) + | (quint64(addr[2]) << 40) + | (quint64(addr[3]) << 32) + | (quint64(addr[4]) << 24) + | (quint64(addr[5]) << 16) + | (quint64(addr[6]) << 8) + | (quint64(addr[7]) << 0); + data.set_src_addr_hi(x); + + x = (quint64(addr[ 8]) << 56) + | (quint64(addr[ 9]) << 48) + | (quint64(addr[10]) << 40) + | (quint64(addr[11]) << 32) + | (quint64(addr[12]) << 24) + | (quint64(addr[13]) << 16) + | (quint64(addr[14]) << 8) + | (quint64(addr[15]) << 0); + data.set_src_addr_lo(x); + break; + } + case ip6_dstAddress: + { + Q_IPV6ADDR addr = QHostAddress(value.toString()).toIPv6Address(); + quint64 x; + + x = (quint64(addr[0]) << 56) + | (quint64(addr[1]) << 48) + | (quint64(addr[2]) << 40) + | (quint64(addr[3]) << 32) + | (quint64(addr[4]) << 24) + | (quint64(addr[5]) << 16) + | (quint64(addr[6]) << 8) + | (quint64(addr[7]) << 0); + data.set_dst_addr_hi(x); + + x = (quint64(addr[ 8]) << 56) + | (quint64(addr[ 9]) << 48) + | (quint64(addr[10]) << 40) + | (quint64(addr[11]) << 32) + | (quint64(addr[12]) << 24) + | (quint64(addr[13]) << 16) + | (quint64(addr[14]) << 8) + | (quint64(addr[15]) << 0); + data.set_dst_addr_lo(x); + break; + } + + // Meta-Fields + case ip6_isOverrideVersion: + { + bool ovr = value.toBool(); + data.set_is_override_version(ovr); + isOk = true; + break; + } + case ip6_isOverridePayloadLength: + { + bool ovr = value.toBool(); + data.set_is_override_payload_length(ovr); + isOk = true; + break; + } + case ip6_isOverrideNextHeader: + { + bool ovr = value.toBool(); + data.set_is_override_next_header(ovr); + isOk = true; + break; + } + + case ip6_srcAddrMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.AddrMode_IsValid(mode)) + data.set_src_addr_mode((OstProto::Ip6::AddrMode) mode); + else + isOk = false; + break; + } + case ip6_srcAddrCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_src_addr_count(count); + break; + } + case ip6_srcAddrPrefix: + { + uint prefix = value.toUInt(&isOk); + if (isOk) + data.set_src_addr_prefix(prefix); + break; + } + + case ip6_dstAddrMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.AddrMode_IsValid(mode)) + data.set_dst_addr_mode((OstProto::Ip6::AddrMode) mode); + else + isOk = false; + break; + } + case ip6_dstAddrCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_dst_addr_count(count); + break; + } + case ip6_dstAddrPrefix: + { + uint prefix = value.toUInt(&isOk); + if (isOk) + data.set_dst_addr_prefix(prefix); + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +bool Ip6Protocol::isProtocolFrameValueVariable() const +{ + if ((data.src_addr_mode() != OstProto::Ip6::kFixed) + || (data.dst_addr_mode() != OstProto::Ip6::kFixed)) + return true; + else + return false; +} + +int Ip6Protocol::protocolFrameVariableCount() const +{ + int count = 1; + + if (data.src_addr_mode() != OstProto::Ip6::kFixed) + count = AbstractProtocol::lcm(count, data.src_addr_count()); + + if (data.dst_addr_mode() != OstProto::Ip6::kFixed) + count = AbstractProtocol::lcm(count, data.dst_addr_count()); + + return count; +} + +quint32 Ip6Protocol::protocolFrameCksum(int streamIndex, + CksumType cksumType) const +{ + if (cksumType == CksumIpPseudo) + { + QByteArray addr; + quint32 sum = 0; + + addr = fieldData(ip6_srcAddress, FieldFrameValue, streamIndex) + .toByteArray(); + Q_ASSERT(addr.size() == 16); + for (int i = 0; i < addr.size(); i+=2) + sum += (quint8(addr.at(i)) << 8) + quint8(addr.at(i+1)); + + addr = fieldData(ip6_dstAddress, FieldFrameValue, streamIndex) + .toByteArray(); + Q_ASSERT(addr.size() == 16); + for (int i = 0; i < addr.size(); i+=2) + sum += (quint8(addr.at(i)) << 8) + quint8(addr.at(i+1)); + + sum += fieldData(ip6_payloadLength, FieldValue, streamIndex) + .toUInt() & 0xFFFF; + sum += fieldData(ip6_nextHeader, FieldValue, streamIndex) + .toUInt() & 0xFF; + + while(sum>>16) + sum = (sum & 0xFFFF) + (sum >> 16); + + return ~sum; + } + return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); +} + diff --git a/common/ip6.h b/common/ip6.h new file mode 100644 index 0000000..63f6e7e --- /dev/null +++ b/common/ip6.h @@ -0,0 +1,112 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP6_H +#define _IP6_H + +#include "abstractprotocol.h" +#include "ip6.pb.h" + +/* +IPv6 Protocol Frame Format - + +-----+----------+-----------------------+ + | Ver | TrfClass | FlowLabel | + | (4) | (8) | (20) | + +-----+-------------+---------+----------+ + | Payload Length | NextHdr | HopLimit | + | (16) | (8) | (8) | + +-------------------+---------+----------+ + | | + | Source Address | + | (128) | + | | + +-----+------+------+------+------+------+ + | | + | Destination Address | + | (128) | + | | + +-----+------+------+------+------+------+ +Figures in brackets represent field width in bits +*/ + +class Ip6Protocol : public AbstractProtocol +{ +public: + enum ip6field + { + // Frame Fields + ip6_version = 0, + ip6_trafficClass, + ip6_flowLabel, + ip6_payloadLength, + ip6_nextHeader, + ip6_hopLimit, + ip6_srcAddress, + ip6_dstAddress, + + // Meta Fields + ip6_isOverrideVersion, + ip6_isOverridePayloadLength, + ip6_isOverrideNextHeader, + + ip6_srcAddrMode, + ip6_srcAddrCount, + ip6_srcAddrPrefix, + + ip6_dstAddrMode, + ip6_dstAddrCount, + ip6_dstAddrPrefix, + + ip6_fieldCount + }; + + Ip6Protocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~Ip6Protocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual ProtocolIdType protocolIdType() const; + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual bool isProtocolFrameValueVariable() const; + virtual int protocolFrameVariableCount() const; + + virtual quint32 protocolFrameCksum(int streamIndex = 0, + CksumType cksumType = CksumIp) const; +private: + OstProto::Ip6 data; +}; + +#endif diff --git a/common/ip6.proto b/common/ip6.proto new file mode 100644 index 0000000..d4831ed --- /dev/null +++ b/common/ip6.proto @@ -0,0 +1,61 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Ip6 Protocol +message Ip6 { + + enum AddrMode { + kFixed = 0; + kIncHost = 1; + kDecHost = 2; + kRandomHost = 3; + } + + optional bool is_override_version = 1; + optional bool is_override_payload_length = 2; + optional bool is_override_next_header = 3; + + optional uint32 version = 4 [default = 0x6]; + optional uint32 traffic_class = 5; + optional uint32 flow_label = 6; + + optional uint32 payload_length = 7; + optional uint32 next_header = 8; + optional uint32 hop_limit = 9 [default = 127]; + + optional uint64 src_addr_hi = 10; + optional uint64 src_addr_lo = 11; + optional AddrMode src_addr_mode = 12 [default = kFixed]; + optional uint32 src_addr_count = 13 [default = 16]; + optional uint32 src_addr_prefix = 14 [default = 64]; + + optional uint64 dst_addr_hi = 15; + optional uint64 dst_addr_lo = 16; + optional AddrMode dst_addr_mode = 17 [default = kFixed]; + optional uint32 dst_addr_count = 18 [default = 16]; + optional uint32 dst_addr_prefix = 19 [default = 64]; +} + +extend Protocol { + optional Ip6 ip6 = 302; +} diff --git a/common/ip6.ui b/common/ip6.ui new file mode 100644 index 0000000..b9c10f2 --- /dev/null +++ b/common/ip6.ui @@ -0,0 +1,467 @@ + + Ip6 + + + + 0 + 0 + 506 + 233 + + + + Form + + + + + + + + Version + + + + + + + false + + + + + + + + + + + + + Qt::Vertical + + + + + + + Payload Length + + + + + + + false + + + + + + + Traffic Class + + + trafficClass + + + + + + + >HH; + + + + + + + + + + Next Header + + + + + + + false + + + HH; + + + + + + + + + + Flow Label + + + flowLabel + + + + + + + >H HH HH; + + + + + + + Hop Limit + + + hopLimit + + + + + + + + + + + + + + + + + + + false + + + + + + Qt::Horizontal + + + + 51 + 20 + + + + + + + + Address + + + + + + + Mode + + + + + + + Count + + + + + + + Prefix + + + + + + + Source + + + + + + + + 1 + 0 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Fixed + + + + + Increment Host + + + + + Decrement Host + + + + + Random Host + + + + + + + + false + + + + 0 + 0 + + + + + 50 + 16777215 + + + + + + + 10 + + + + + + + false + + + + 0 + 0 + + + + + 50 + 16777215 + + + + /009; + + + /64 + + + + + + + Destination + + + + + + + + 1 + 0 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Fixed + + + + + Increment Host + + + + + Decrement Host + + + + + Random Host + + + + + + + + false + + + + 0 + 0 + + + + + 50 + 16777215 + + + + + + + 10 + + + + + + + false + + + + 0 + 0 + + + + + 50 + 16777215 + + + + /009; + + + /64 + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + isVersionOverride + version + trafficClass + flowLabel + isPayloadLengthOverride + payloadLength + isNextHeaderOverride + nextHeader + hopLimit + srcAddr + srcAddrModeCombo + srcAddrCount + srcAddrPrefix + dstAddr + dstAddrModeCombo + dstAddrCount + dstAddrPrefix + + + + + isVersionOverride + toggled(bool) + version + setEnabled(bool) + + + 67 + 22 + + + 195 + 11 + + + + + isPayloadLengthOverride + toggled(bool) + payloadLength + setEnabled(bool) + + + 319 + 28 + + + 493 + 29 + + + + + isNextHeaderOverride + toggled(bool) + nextHeader + setEnabled(bool) + + + 316 + 41 + + + 348 + 46 + + + + + diff --git a/common/ip6config.cpp b/common/ip6config.cpp new file mode 100644 index 0000000..1ddd20b --- /dev/null +++ b/common/ip6config.cpp @@ -0,0 +1,233 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "ip6config.h" +#include "ip6.h" +#include "ipv6addressvalidator.h" +#include + +Ip6ConfigForm::Ip6ConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); + + version->setValidator(new QIntValidator(0, 0xF, this)); + payloadLength->setValidator(new QIntValidator(0, 0xFFFF, this)); + hopLimit->setValidator(new QIntValidator(0, 0xFF, this)); + + srcAddr->setValidator(new IPv6AddressValidator(this)); + srcAddrCount->setValidator(new QIntValidator(this)); + //srcAddrPrefix->setValidator(new QIntValidator(0, 128, this)); + + dstAddr->setValidator(new IPv6AddressValidator(this)); + dstAddrCount->setValidator(new QIntValidator(this)); + //dstAddrPrefix->setValidator(new QIntValidator(0, 128, this)); +} + +AbstractProtocolConfigForm* Ip6ConfigForm::createInstance() +{ + return new Ip6ConfigForm; +} + +void Ip6ConfigForm::on_srcAddr_editingFinished() +{ + srcAddr->setText(QHostAddress(srcAddr->text()).toString()); +} + +void Ip6ConfigForm::on_dstAddr_editingFinished() +{ + dstAddr->setText(QHostAddress(dstAddr->text()).toString()); +} + +void Ip6ConfigForm::on_srcAddrModeCombo_currentIndexChanged(int index) +{ + bool enabled = (index > 0); + + srcAddrCount->setEnabled(enabled); + srcAddrPrefix->setEnabled(enabled); +} + +void Ip6ConfigForm::on_dstAddrModeCombo_currentIndexChanged(int index) +{ + bool enabled = (index > 0); + + dstAddrCount->setEnabled(enabled); + dstAddrPrefix->setEnabled(enabled); +} + +void Ip6ConfigForm::loadWidget(AbstractProtocol *ip6Proto) +{ + isVersionOverride->setChecked( + ip6Proto->fieldData( + Ip6Protocol::ip6_isOverrideVersion, + AbstractProtocol::FieldValue + ).toBool()); + version->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_version, + AbstractProtocol::FieldValue + ).toString()); + + trafficClass->setText(uintToHexStr( + ip6Proto->fieldData( + Ip6Protocol::ip6_trafficClass, + AbstractProtocol::FieldValue + ).toUInt(), 1)); + + flowLabel->setText(QString("%1").arg( + ip6Proto->fieldData( + Ip6Protocol::ip6_flowLabel, + AbstractProtocol::FieldValue + ).toUInt(), 5, BASE_HEX, QChar('0'))); + + isPayloadLengthOverride->setChecked( + ip6Proto->fieldData( + Ip6Protocol::ip6_isOverridePayloadLength, + AbstractProtocol::FieldValue + ).toBool()); + payloadLength->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_payloadLength, + AbstractProtocol::FieldValue + ).toString()); + + isNextHeaderOverride->setChecked( + ip6Proto->fieldData( + Ip6Protocol::ip6_isOverrideNextHeader, + AbstractProtocol::FieldValue + ).toBool()); + nextHeader->setText(uintToHexStr( + ip6Proto->fieldData( + Ip6Protocol::ip6_nextHeader, + AbstractProtocol::FieldValue + ).toUInt(), 1)); + + hopLimit->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_hopLimit, + AbstractProtocol::FieldValue + ).toString()); + + srcAddr->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_srcAddress, + AbstractProtocol::FieldTextValue + ).toString()); + srcAddrModeCombo->setCurrentIndex( + ip6Proto->fieldData( + Ip6Protocol::ip6_srcAddrMode, + AbstractProtocol::FieldValue + ).toUInt()); + srcAddrCount->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_srcAddrCount, + AbstractProtocol::FieldValue + ).toString()); + srcAddrPrefix->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_srcAddrPrefix, + AbstractProtocol::FieldValue + ).toString()); + + dstAddr->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_dstAddress, + AbstractProtocol::FieldTextValue + ).toString()); + dstAddrModeCombo->setCurrentIndex( + ip6Proto->fieldData( + Ip6Protocol::ip6_dstAddrMode, + AbstractProtocol::FieldValue + ).toUInt()); + dstAddrCount->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_dstAddrCount, + AbstractProtocol::FieldValue + ).toString()); + dstAddrPrefix->setText( + ip6Proto->fieldData( + Ip6Protocol::ip6_dstAddrPrefix, + AbstractProtocol::FieldValue + ).toString()); +} + +void Ip6ConfigForm::storeWidget(AbstractProtocol *ip6Proto) +{ + bool isOk; + + ip6Proto->setFieldData( + Ip6Protocol::ip6_isOverrideVersion, + isVersionOverride->isChecked()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_version, + version->text()); + + ip6Proto->setFieldData( + Ip6Protocol::ip6_trafficClass, + trafficClass->text().remove(QChar(' ')).toUInt(&isOk, BASE_HEX)); + + ip6Proto->setFieldData( + Ip6Protocol::ip6_flowLabel, + flowLabel->text().remove(QChar(' ')).toUInt(&isOk, BASE_HEX)); + + ip6Proto->setFieldData( + Ip6Protocol::ip6_isOverridePayloadLength, + isPayloadLengthOverride->isChecked()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_payloadLength, + payloadLength->text()); + + ip6Proto->setFieldData( + Ip6Protocol::ip6_isOverrideNextHeader, + isNextHeaderOverride->isChecked()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_nextHeader, + nextHeader->text().remove(QChar(' ')).toUInt(&isOk, BASE_HEX)); + + ip6Proto->setFieldData( + Ip6Protocol::ip6_hopLimit, + hopLimit->text()); + + ip6Proto->setFieldData( + Ip6Protocol::ip6_srcAddress, + srcAddr->text()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_srcAddrMode, + srcAddrModeCombo->currentIndex()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_srcAddrCount, + srcAddrCount->text()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_srcAddrPrefix, + srcAddrPrefix->text()); + + ip6Proto->setFieldData( + Ip6Protocol::ip6_dstAddress, + dstAddr->text()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_dstAddrMode, + dstAddrModeCombo->currentIndex()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_dstAddrCount, + dstAddrCount->text()); + ip6Proto->setFieldData( + Ip6Protocol::ip6_dstAddrPrefix, + dstAddrPrefix->text()); +} + diff --git a/common/ip6config.h b/common/ip6config.h new file mode 100644 index 0000000..0ea08c1 --- /dev/null +++ b/common/ip6config.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _IP6_CONFIG_H +#define _IP6_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_ip6.h" + +class Ip6ConfigForm : + public AbstractProtocolConfigForm, + private Ui::Ip6 +{ + Q_OBJECT +public: + Ip6ConfigForm(QWidget *parent = 0); + static AbstractProtocolConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *ip6Proto); + virtual void storeWidget(AbstractProtocol *ip6Proto); + +private slots: + void on_srcAddr_editingFinished(); + void on_dstAddr_editingFinished(); + void on_srcAddrModeCombo_currentIndexChanged(int index); + void on_dstAddrModeCombo_currentIndexChanged(int index); +}; + +#endif diff --git a/common/ip6over4.h b/common/ip6over4.h new file mode 100644 index 0000000..08ee19b --- /dev/null +++ b/common/ip6over4.h @@ -0,0 +1,30 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_6_OVER_4_H +#define _IP_6_OVER_4_H + +#include "comboprotocol.h" +#include "ip4.h" +#include "ip6.h" + +typedef ComboProtocol Ip6over4Protocol; + +#endif diff --git a/common/ip6over4.proto b/common/ip6over4.proto new file mode 100644 index 0000000..b8b0afd --- /dev/null +++ b/common/ip6over4.proto @@ -0,0 +1,31 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// IP Tunelling - IP 6over4 +message Ip6over4 { + // Empty since this is a 'combo' protocol +} + +extend Protocol { + optional Ip6over4 ip6over4 = 303; +} diff --git a/common/ip6over4config.h b/common/ip6over4config.h new file mode 100644 index 0000000..b3c2390 --- /dev/null +++ b/common/ip6over4config.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_6_OVER_4_CONFIG_H +#define _IP_6_OVER_4_CONFIG_H + +#include "comboprotocolconfig.h" +#include "ip4config.h" +#include "ip6config.h" +#include "ip4.h" +#include "ip6.h" + +typedef ComboProtocolConfigForm < + OstProto::Protocol::kIp6over4FieldNumber, + Ip4ConfigForm, Ip6ConfigForm, + Ip4Protocol, Ip6Protocol + > Ip6over4ConfigForm; + +#endif diff --git a/common/ip6over6.h b/common/ip6over6.h new file mode 100644 index 0000000..133d4f9 --- /dev/null +++ b/common/ip6over6.h @@ -0,0 +1,91 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_6_OVER_6_H +#define _IP_6_OVER_6_H + +#include "ip6over6.pb.h" + +#include "comboprotocol.h" +#include "ip6.h" + +typedef ComboProtocol Ip6over6Combo; + +class Ip6over6Protocol : public Ip6over6Combo +{ +public: + Ip6over6Protocol(StreamBase *stream, AbstractProtocol *parent = 0) + : Ip6over6Combo(stream, parent) + { + } + + static Ip6over6Protocol* createInstance(StreamBase *stream, + AbstractProtocol *parent) + { + return new Ip6over6Protocol(stream, parent); + } + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const + { + OstProto::Protocol tempProto; + + protoA->protoDataCopyInto(tempProto); + protocol.MutableExtension(OstProto::ip6over6) + ->MutableExtension(OstProto::ip6_outer) + ->CopyFrom(tempProto.GetExtension(OstProto::ip6)); + + tempProto.Clear(); + + protoB->protoDataCopyInto(tempProto); + protocol.MutableExtension(OstProto::ip6over6) + ->MutableExtension(OstProto::ip6_inner) + ->CopyFrom(tempProto.GetExtension(OstProto::ip6)); + + protocol.mutable_protocol_id()->set_id(protocolNumber()); + } + + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol) + { + if (protocol.protocol_id().id() == protocolNumber() + && protocol.HasExtension(OstProto::ip6over6)) + { + OstProto::Protocol tempProto; + + // NOTE: To use protoX->protoDataCopyFrom() we need to arrange + // so that it sees its own protocolNumber() and its own extension + // in 'protocol' + tempProto.mutable_protocol_id()->set_id(protoA->protocolNumber()); + tempProto.MutableExtension(OstProto::ip6)->CopyFrom( + protocol.GetExtension(OstProto::ip6over6).GetExtension( + OstProto::ip6_outer)); + protoA->protoDataCopyFrom(tempProto); + + tempProto.Clear(); + + tempProto.mutable_protocol_id()->set_id(protoB->protocolNumber()); + tempProto.MutableExtension(OstProto::ip6)->CopyFrom( + protocol.GetExtension(OstProto::ip6over6).GetExtension( + OstProto::ip6_inner)); + protoB->protoDataCopyFrom(tempProto); + } + } +}; + +#endif diff --git a/common/ip6over6.proto b/common/ip6over6.proto new file mode 100644 index 0000000..f65f6ea --- /dev/null +++ b/common/ip6over6.proto @@ -0,0 +1,37 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; +import "ip6.proto"; + +package OstProto; + +// IP Tunnelling - IP 6over6 +message Ip6over6 { + extensions 1 to 2; +} + +extend Ip6over6 { + optional Ip6 ip6_outer = 1; + optional Ip6 ip6_inner = 2; +} + +extend Protocol { + optional Ip6over6 ip6over6 = 306; +} diff --git a/common/ip6over6config.h b/common/ip6over6config.h new file mode 100644 index 0000000..4324fe7 --- /dev/null +++ b/common/ip6over6config.h @@ -0,0 +1,33 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_6_OVER_6_CONFIG_H +#define _IP_6_OVER_6_CONFIG_H + +#include "comboprotocolconfig.h" +#include "ip6config.h" +#include "ip6.h" + +typedef ComboProtocolConfigForm < + OstProto::Protocol::kIp6over6FieldNumber, + Ip6ConfigForm, Ip6ConfigForm, + Ip6Protocol, Ip6Protocol + > Ip6over6ConfigForm; + +#endif diff --git a/common/ip6pdml.cpp b/common/ip6pdml.cpp new file mode 100644 index 0000000..2f3a7f8 --- /dev/null +++ b/common/ip6pdml.cpp @@ -0,0 +1,76 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "ip6pdml.h" + +#include "ip6.pb.h" + +PdmlIp6Protocol::PdmlIp6Protocol() +{ + ostProtoId_ = OstProto::Protocol::kIp6FieldNumber; + + fieldMap_.insert("ipv6.version", OstProto::Ip6::kVersionFieldNumber); + fieldMap_.insert("ipv6.class", OstProto::Ip6::kTrafficClassFieldNumber); + fieldMap_.insert("ipv6.flow", OstProto::Ip6::kFlowLabelFieldNumber); + fieldMap_.insert("ipv6.plen", OstProto::Ip6::kPayloadLengthFieldNumber); + fieldMap_.insert("ipv6.nxt", OstProto::Ip6::kNextHeaderFieldNumber); + fieldMap_.insert("ipv6.hlim", OstProto::Ip6::kHopLimitFieldNumber); + + // ipv6.src and ipv6.dst handled as unknown fields +} + +PdmlProtocol* PdmlIp6Protocol::createInstance() +{ + return new PdmlIp6Protocol(); +} + +void PdmlIp6Protocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + bool isOk; + + if (name == "ipv6.src") + { + OstProto::Ip6 *ip6 = pbProto->MutableExtension(OstProto::ip6); + QString addrHexStr = attributes.value("value").toString(); + + ip6->set_src_addr_hi(addrHexStr.left(16).toULongLong(&isOk, kBaseHex)); + ip6->set_src_addr_lo(addrHexStr.right(16).toULongLong(&isOk, kBaseHex)); + } + else if (name == "ipv6.dst") + { + OstProto::Ip6 *ip6 = pbProto->MutableExtension(OstProto::ip6); + QString addrHexStr = attributes.value("value").toString(); + + ip6->set_dst_addr_hi(addrHexStr.left(16).toULongLong(&isOk, kBaseHex)); + ip6->set_dst_addr_lo(addrHexStr.right(16).toULongLong(&isOk, kBaseHex)); + } +} + +void PdmlIp6Protocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream* /*stream*/) +{ + OstProto::Ip6 *ip6 = pbProto->MutableExtension(OstProto::ip6); + + ip6->set_is_override_version(true); + ip6->set_is_override_payload_length(true); + ip6->set_is_override_next_header(true); +} + diff --git a/common/ip6pdml.h b/common/ip6pdml.h new file mode 100644 index 0000000..4f766b4 --- /dev/null +++ b/common/ip6pdml.h @@ -0,0 +1,39 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP6_PDML_H +#define _IP6_PDML_H + +#include "pdmlprotocol.h" + +class PdmlIp6Protocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); +protected: + PdmlIp6Protocol(); +}; + +#endif diff --git a/common/iputils.h b/common/iputils.h new file mode 100644 index 0000000..0d6a067 --- /dev/null +++ b/common/iputils.h @@ -0,0 +1,122 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IP_UTILS_H +#define _IP_UTILS_H + +namespace ipUtils { +enum AddrMode { + kFixed = 0, + kIncrement = 1, + kDecrement = 2, + kRandom = 3 +}; + +quint32 inline ipAddress(quint32 baseIp, int prefix, AddrMode mode, int count, + int index) +{ + int u; + quint32 mask = ((1< 64) { + p = 64; + q = prefix - 64; + } else { + p = prefix; + q = 0; + } + if (p > 0) + maskHi = ~((quint64(1) << p) - 1); + if (q > 0) + maskLo = ~((quint64(1) << q) - 1); + prefixHi = baseIpHi & maskHi; + prefixLo = baseIpLo & maskLo; + if (mode == kIncrement) { + hostHi = ((baseIpHi & ~maskHi) + 0) & ~maskHi; + hostLo = ((baseIpLo & ~maskLo) + u) & ~maskLo; + } + else if (mode == kDecrement) { + hostHi = ((baseIpHi & ~maskHi) - 0) & ~maskHi; + hostLo = ((baseIpLo & ~maskLo) - u) & ~maskLo; + } + else if (mode==kRandom) { + hostHi = qrand() & ~maskHi; + hostLo = qrand() & ~maskLo; + } + ipHi = prefixHi | hostHi; + ipLo = prefixLo | hostLo; + break; + default: + qWarning("Unhandled mode = %d", mode); + } +} + +} // namespace ipUtils +#endif diff --git a/common/ipv4addressdelegate.h b/common/ipv4addressdelegate.h new file mode 100644 index 0000000..9e80d17 --- /dev/null +++ b/common/ipv4addressdelegate.h @@ -0,0 +1,58 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _IPV4_ADDRESS_DELEGATE +#define _IPV4_ADDRESS_DELEGATE + +#include +#include + +class IPv4AddressDelegate : public QItemDelegate +{ + Q_OBJECT +public: + IPv4AddressDelegate(QObject *parent = 0); + ~IPv4AddressDelegate(); + + QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; +}; + +inline IPv4AddressDelegate::IPv4AddressDelegate(QObject *parent) + : QItemDelegate(parent) +{ +} + +inline IPv4AddressDelegate::~IPv4AddressDelegate() +{ +} + +inline QWidget* IPv4AddressDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QLineEdit *ipEdit; + + ipEdit = static_cast(QItemDelegate::createEditor( + parent, option, index)); + + ipEdit->setInputMask("009.009.009.009;"); // FIXME: use validator + + return ipEdit; +} +#endif + diff --git a/common/ipv6addressdelegate.h b/common/ipv6addressdelegate.h new file mode 100644 index 0000000..9e3c30e --- /dev/null +++ b/common/ipv6addressdelegate.h @@ -0,0 +1,60 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _IPV4_ADDRESS_DELEGATE +#define _IPV4_ADDRESS_DELEGATE + +#include "ipv6addressvalidator.h" + +#include +#include + +class IPv6AddressDelegate : public QItemDelegate +{ + Q_OBJECT +public: + IPv6AddressDelegate(QObject *parent = 0); + ~IPv6AddressDelegate(); + + QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, + const QModelIndex &index) const; +}; + +inline IPv6AddressDelegate::IPv6AddressDelegate(QObject *parent) + : QItemDelegate(parent) +{ +} + +inline IPv6AddressDelegate::~IPv6AddressDelegate() +{ +} + +inline QWidget* IPv6AddressDelegate::createEditor(QWidget *parent, + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QLineEdit *ipEdit; + + ipEdit = static_cast(QItemDelegate::createEditor( + parent, option, index)); + + ipEdit->setValidator(new IPv6AddressValidator(ipEdit)); + + return ipEdit; +} +#endif + diff --git a/common/ipv6addressvalidator.h b/common/ipv6addressvalidator.h new file mode 100644 index 0000000..ffbd7d5 --- /dev/null +++ b/common/ipv6addressvalidator.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _IPV6_ADDRESS_VALIDATOR_H +#define _IPV6_ADDRESS_VALIDATOR_H + +#include +#include + +class IPv6AddressValidator : public QValidator +{ +public: + IPv6AddressValidator(QObject *parent = 0) + : QValidator(parent) + { + _ip6ValidChars.setPattern("[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{0,4}){0,7}"); + } + ~IPv6AddressValidator() {} + + virtual QValidator::State validate(QString &input, int& /*pos*/) const + { + QValidator::State state; + QHostAddress addr(input); + + //qDebug("%s: %s (%d)", __FUNCTION__, input.toAscii().constData(), pos); + + if (addr.protocol() == QAbstractSocket::IPv6Protocol) + state = Acceptable; + else + if (_ip6ValidChars.exactMatch(input)) + state = Intermediate; + else + state = Invalid; + //qDebug("%s(%d): %s (%d), ", __FUNCTION__, state, + //input.toAscii().constData(), pos); + return state; + } + virtual void fixup(QString &input) const + { + input.append("::"); + QHostAddress addr(input); + int len = input.size(); + + //qDebug("%s: %s", __FUNCTION__, input.toAscii().constData()); + + while (addr.protocol() != QAbstractSocket::IPv6Protocol) + { + len--; + Q_ASSERT(len >= 0); + addr.setAddress(input.left(len)); + } + + input = addr.toString(); + } +private: + QRegExp _ip6ValidChars; +}; + +#endif diff --git a/common/llc.cpp b/common/llc.cpp new file mode 100644 index 0000000..67b370d --- /dev/null +++ b/common/llc.cpp @@ -0,0 +1,264 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "llc.h" + +LlcProtocol::LlcProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +LlcProtocol::~LlcProtocol() +{ +} + +AbstractProtocol* LlcProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new LlcProtocol(stream, parent); +} + +quint32 LlcProtocol::protocolNumber() const +{ + return OstProto::Protocol::kLlcFieldNumber; +} + +void LlcProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::llc)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void LlcProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::llc)) + data.MergeFrom(protocol.GetExtension(OstProto::llc)); +} + +QString LlcProtocol::name() const +{ + return QString("802.3 Logical Link Control"); +} + +QString LlcProtocol::shortName() const +{ + return QString("LLC"); +} + +AbstractProtocol::ProtocolIdType LlcProtocol::protocolIdType() const +{ + return ProtocolIdLlc; +} + +int LlcProtocol::fieldCount() const +{ + return llc_fieldCount; +} + +AbstractProtocol::FieldFlags LlcProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case llc_dsap: + case llc_ssap: + case llc_ctl: + break; + + case llc_is_override_dsap: + case llc_is_override_ssap: + case llc_is_override_ctl: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + break; + } + + return flags; +} + +QVariant LlcProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + quint32 id; + quint8 dsap, ssap, ctl; + + id = payloadProtocolId(ProtocolIdLlc); + dsap = data.is_override_dsap() ? data.dsap() : (id >> 16) & 0xFF; + ssap = data.is_override_ssap() ? data.ssap() : (id >> 8) & 0xFF; + ctl = data.is_override_ctl() ? data.ctl() : (id >> 0) & 0xFF; + + switch (index) + { + case llc_dsap: + switch(attrib) + { + case FieldName: + return QString("DSAP"); + case FieldValue: + return dsap; + case FieldTextValue: + return QString("%1").arg(dsap, 2, BASE_HEX, QChar('0')); + case FieldFrameValue: + return QByteArray(1, (char)(dsap)); + default: + break; + } + break; + case llc_ssap: + switch(attrib) + { + case FieldName: + return QString("SSAP"); + case FieldValue: + return ssap; + case FieldTextValue: + return QString("%1").arg(ssap, 2, BASE_HEX, QChar('0')); + case FieldFrameValue: + return QByteArray(1, (char)(ssap)); + default: + break; + } + break; + case llc_ctl: + switch(attrib) + { + case FieldName: + return QString("Control"); + case FieldValue: + return ctl; + case FieldTextValue: + return QString("%1").arg(ctl, 2, BASE_HEX, QChar('0')); + case FieldFrameValue: + return QByteArray(1, (char)(ctl)); + default: + break; + } + break; + + + // Meta fields + case llc_is_override_dsap: + { + switch(attrib) + { + case FieldValue: + return data.is_override_dsap(); + default: + break; + } + break; + } + case llc_is_override_ssap: + { + switch(attrib) + { + case FieldValue: + return data.is_override_ssap(); + default: + break; + } + break; + } + case llc_is_override_ctl: + { + switch(attrib) + { + case FieldValue: + return data.is_override_ctl(); + default: + break; + } + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool LlcProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + return false; + + switch (index) + { + case llc_dsap: + { + uint dsap = value.toUInt(&isOk) & 0xFF; + if (isOk) + data.set_dsap(dsap); + break; + } + case llc_ssap: + { + uint ssap = value.toUInt(&isOk) & 0xFF; + if (isOk) + data.set_ssap(ssap); + break; + } + case llc_ctl: + { + uint ctl = value.toUInt(&isOk) & 0xFF; + if (isOk) + data.set_ctl(ctl); + break; + } + case llc_is_override_dsap: + { + bool ovr = value.toBool(); + data.set_is_override_dsap(ovr); + isOk = true; + break; + } + case llc_is_override_ssap: + { + bool ovr = value.toBool(); + data.set_is_override_ssap(ovr); + isOk = true; + break; + } + case llc_is_override_ctl: + { + bool ovr = value.toBool(); + data.set_is_override_ctl(ovr); + isOk = true; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + return isOk; +} diff --git a/common/llc.h b/common/llc.h new file mode 100644 index 0000000..a723177 --- /dev/null +++ b/common/llc.h @@ -0,0 +1,71 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _LLC_H +#define _LLC_H + +#include "abstractprotocol.h" + +#include "llc.pb.h" + +class LlcProtocol : public AbstractProtocol +{ +public: + enum llcfield + { + llc_dsap = 0, + llc_ssap, + llc_ctl, + + // Meta fields + llc_is_override_dsap, + llc_is_override_ssap, + llc_is_override_ctl, + + llc_fieldCount + }; + + LlcProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~LlcProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual ProtocolIdType protocolIdType() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + +private: + OstProto::Llc data; +}; + +#endif diff --git a/common/llc.proto b/common/llc.proto new file mode 100644 index 0000000..360b935 --- /dev/null +++ b/common/llc.proto @@ -0,0 +1,36 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +message Llc { + optional bool is_override_dsap = 4; + optional bool is_override_ssap = 5; + optional bool is_override_ctl = 6; + + optional uint32 dsap = 1; + optional uint32 ssap = 2; + optional uint32 ctl = 3; +} + +extend Protocol { + optional Llc llc = 202; +} diff --git a/common/llc.ui b/common/llc.ui new file mode 100644 index 0000000..e61f54e --- /dev/null +++ b/common/llc.ui @@ -0,0 +1,161 @@ + + llc + + + + 0 + 0 + 396 + 98 + + + + + 0 + 0 + + + + Form + + + + + + LLC + + + + + + DSAP + + + + + + + false + + + >HH; + + + + + + + SSAP + + + + + + + false + + + >HH; + + + + + + + Control + + + + + + + false + + + >HH; + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + cbOverrideDsap + toggled(bool) + leDsap + setEnabled(bool) + + + 54 + 34 + + + 92 + 33 + + + + + cbOverrideSsap + toggled(bool) + leSsap + setEnabled(bool) + + + 167 + 34 + + + 192 + 33 + + + + + cbOverrideControl + toggled(bool) + leControl + setEnabled(bool) + + + 285 + 34 + + + 310 + 33 + + + + + diff --git a/common/llcconfig.cpp b/common/llcconfig.cpp new file mode 100644 index 0000000..6ba785c --- /dev/null +++ b/common/llcconfig.cpp @@ -0,0 +1,100 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "llcconfig.h" +#include "llc.h" + +LlcConfigForm::LlcConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); +} + +LlcConfigForm::~LlcConfigForm() +{ +} + +LlcConfigForm* LlcConfigForm::createInstance() +{ + return new LlcConfigForm; +} + +void LlcConfigForm::loadWidget(AbstractProtocol *proto) +{ + cbOverrideDsap->setChecked( + proto->fieldData( + LlcProtocol::llc_is_override_dsap, + AbstractProtocol::FieldValue + ).toBool()); + leDsap->setText(uintToHexStr( + proto->fieldData( + LlcProtocol::llc_dsap, + AbstractProtocol::FieldValue + ).toUInt(), 1)); + + cbOverrideSsap->setChecked( + proto->fieldData( + LlcProtocol::llc_is_override_ssap, + AbstractProtocol::FieldValue + ).toBool()); + leSsap->setText(uintToHexStr( + proto->fieldData( + LlcProtocol::llc_ssap, + AbstractProtocol::FieldValue + ).toUInt(), 1)); + + cbOverrideControl->setChecked( + proto->fieldData( + LlcProtocol::llc_is_override_ctl, + AbstractProtocol::FieldValue + ).toBool()); + leControl->setText(uintToHexStr( + proto->fieldData( + LlcProtocol::llc_ctl, + AbstractProtocol::FieldValue + ).toUInt(), 1)); +} + +void +LlcConfigForm::storeWidget(AbstractProtocol *proto) +{ + bool isOk; + + proto->setFieldData( + LlcProtocol::llc_is_override_dsap, + cbOverrideDsap->isChecked()); + proto->setFieldData( + LlcProtocol::llc_dsap, + leDsap->text().toUInt(&isOk, BASE_HEX)); + + proto->setFieldData( + LlcProtocol::llc_is_override_ssap, + cbOverrideSsap->isChecked()); + proto->setFieldData( + LlcProtocol::llc_ssap, + leSsap->text().toUInt(&isOk, BASE_HEX)); + + proto->setFieldData( + LlcProtocol::llc_is_override_ctl, + cbOverrideControl->isChecked()); + proto->setFieldData( + LlcProtocol::llc_ctl, + leControl->text().toUInt(&isOk, BASE_HEX)); +} + diff --git a/common/llcconfig.h b/common/llcconfig.h new file mode 100644 index 0000000..08bd3f4 --- /dev/null +++ b/common/llcconfig.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _LLC_CONFIG_H +#define _LLC_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_llc.h" + +class LlcConfigForm : + public AbstractProtocolConfigForm, + private Ui::llc +{ + Q_OBJECT +public: + LlcConfigForm(QWidget *parent = 0); + virtual ~LlcConfigForm(); + + static LlcConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +}; + +#endif diff --git a/common/llcpdml.cpp b/common/llcpdml.cpp new file mode 100644 index 0000000..a5584cc --- /dev/null +++ b/common/llcpdml.cpp @@ -0,0 +1,80 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "llcpdml.h" + +#include "llc.pb.h" +#include "snap.pb.h" + +#include + +PdmlLlcProtocol::PdmlLlcProtocol() +{ + ostProtoId_ = OstProto::Protocol::kLlcFieldNumber; + + fieldMap_.insert("llc.dsap", OstProto::Llc::kDsapFieldNumber); + fieldMap_.insert("llc.ssap", OstProto::Llc::kSsapFieldNumber); + fieldMap_.insert("llc.control", OstProto::Llc::kCtlFieldNumber); +} + +PdmlProtocol* PdmlLlcProtocol::createInstance() +{ + return new PdmlLlcProtocol(); +} + +void PdmlLlcProtocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol* /*pbProto*/, OstProto::Stream *stream) +{ + if (name == "llc.oui") + { + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kSnapFieldNumber); + + OstProto::Snap *snap = proto->MutableExtension(OstProto::snap); + + bool isOk; + snap->set_oui(attributes.value("value").toString() + .toUInt(&isOk, kBaseHex)); + snap->set_is_override_oui(true); + } + else if ((name == "llc.type") || (name.contains(QRegExp("llc\\..*pid")))) + { + OstProto::Snap *snap = stream->mutable_protocol( + stream->protocol_size()-1)->MutableExtension(OstProto::snap); + + bool isOk; + snap->set_type(attributes.value("value").toString() + .toUInt(&isOk, kBaseHex)); + snap->set_is_override_type(true); + } +} + +void PdmlLlcProtocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream* /*stream*/) +{ + OstProto::Llc *llc = pbProto->MutableExtension(OstProto::llc); + + llc->set_is_override_dsap(true); + llc->set_is_override_ssap(true); + llc->set_is_override_ctl(true); +} + diff --git a/common/llcpdml.h b/common/llcpdml.h new file mode 100644 index 0000000..ace0dcb --- /dev/null +++ b/common/llcpdml.h @@ -0,0 +1,39 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _LLC_PDML_H +#define _LLC_PDML_H + +#include "pdmlprotocol.h" + +class PdmlLlcProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); +protected: + PdmlLlcProtocol(); +}; + +#endif diff --git a/common/mac.cpp b/common/mac.cpp new file mode 100644 index 0000000..70d047b --- /dev/null +++ b/common/mac.cpp @@ -0,0 +1,351 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "mac.h" + +#include + +#define uintToMacStr(num) \ + QString("%1").arg(num, 6*2, BASE_HEX, QChar('0')) \ + .replace(QRegExp("([0-9a-fA-F]{2}\\B)"), "\\1:").toUpper() + +MacProtocol::MacProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +MacProtocol::~MacProtocol() +{ +} + +AbstractProtocol* MacProtocol::createInstance(StreamBase *stream + , AbstractProtocol *parent) +{ + return new MacProtocol(stream, parent); +} + +quint32 MacProtocol::protocolNumber() const +{ + return OstProto::Protocol::kMacFieldNumber; +} + +void MacProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::mac)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void MacProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::mac)) + data.MergeFrom(protocol.GetExtension(OstProto::mac)); +} + +QString MacProtocol::name() const +{ + return QString("Media Access Protocol"); +} + +QString MacProtocol::shortName() const +{ + return QString("MAC"); +} + +int MacProtocol::fieldCount() const +{ + return mac_fieldCount; +} + +AbstractProtocol::FieldFlags MacProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case mac_dstAddr: + case mac_srcAddr: + break; + + case mac_dstMacMode: + case mac_dstMacCount: + case mac_dstMacStep: + case mac_srcMacMode: + case mac_srcMacCount: + case mac_srcMacStep: + flags &= ~FrameField; + flags |= MetaField; + break; + } + + return flags; +} + +QVariant MacProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case mac_dstAddr: + { + int u; + quint64 dstMac = 0; + + switch (data.dst_mac_mode()) + { + case OstProto::Mac::e_mm_fixed: + dstMac = data.dst_mac(); + break; + case OstProto::Mac::e_mm_inc: + u = (streamIndex % data.dst_mac_count()) * + data.dst_mac_step(); + dstMac = data.dst_mac() + u; + break; + case OstProto::Mac::e_mm_dec: + u = (streamIndex % data.dst_mac_count()) * + data.dst_mac_step(); + dstMac = data.dst_mac() - u; + break; + default: + qWarning("Unhandled dstMac_mode %d", data.dst_mac_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Desination"); + case FieldValue: + return dstMac; + case FieldTextValue: + return uintToMacStr(dstMac); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(8); + qToBigEndian(dstMac, (uchar*) fv.data()); + fv.remove(0, 2); + return fv; + } + default: + break; + } + break; + } + case mac_srcAddr: + { + int u; + quint64 srcMac = 0; + + switch (data.src_mac_mode()) + { + case OstProto::Mac::e_mm_fixed: + srcMac = data.src_mac(); + break; + case OstProto::Mac::e_mm_inc: + u = (streamIndex % data.src_mac_count()) * + data.src_mac_step(); + srcMac = data.src_mac() + u; + break; + case OstProto::Mac::e_mm_dec: + u = (streamIndex % data.src_mac_count()) * + data.src_mac_step(); + srcMac = data.src_mac() - u; + break; + default: + qWarning("Unhandled srcMac_mode %d", data.src_mac_mode()); + } + + switch(attrib) + { + case FieldName: + return QString("Source"); + case FieldValue: + return srcMac; + case FieldTextValue: + return uintToMacStr(srcMac); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(8); + qToBigEndian(srcMac, (uchar*) fv.data()); + fv.remove(0, 2); + return fv; + } + default: + break; + } + break; + } + + // Meta fields + case mac_dstMacMode: + switch(attrib) + { + case FieldValue: return data.dst_mac_mode(); + default: break; + } + break; + case mac_dstMacCount: + switch(attrib) + { + case FieldValue: return data.dst_mac_count(); + default: break; + } + break; + case mac_dstMacStep: + switch(attrib) + { + case FieldValue: return data.dst_mac_step(); + default: break; + } + break; + case mac_srcMacMode: + switch(attrib) + { + case FieldValue: return data.src_mac_mode(); + default: break; + } + break; + case mac_srcMacCount: + switch(attrib) + { + case FieldValue: return data.src_mac_count(); + default: break; + } + break; + case mac_srcMacStep: + switch(attrib) + { + case FieldValue: return data.src_mac_step(); + default: break; + } + break; + default: + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool MacProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case mac_dstAddr: + { + quint64 mac = value.toString().toULongLong(&isOk, BASE_HEX); + if (isOk) + data.set_dst_mac(mac); + break; + } + case mac_srcAddr: + { + quint64 mac = value.toString().toULongLong(&isOk, BASE_HEX); + if (isOk) + data.set_src_mac(mac); + break; + } + + // Meta-Fields + case mac_dstMacMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.MacAddrMode_IsValid(mode)) + data.set_dst_mac_mode((OstProto::Mac::MacAddrMode) mode); + else + isOk = false; + break; + } + case mac_dstMacCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_dst_mac_count(count); + break; + } + case mac_dstMacStep: + { + uint step = value.toUInt(&isOk); + if (isOk) + data.set_dst_mac_step(step); + break; + } + case mac_srcMacMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.MacAddrMode_IsValid(mode)) + data.set_src_mac_mode((OstProto::Mac::MacAddrMode) mode); + else + isOk = false; + break; + } + case mac_srcMacCount: + { + uint count = value.toUInt(&isOk); + if (isOk) + data.set_src_mac_count(count); + break; + } + case mac_srcMacStep: + { + uint step = value.toUInt(&isOk); + if (isOk) + data.set_src_mac_step(step); + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +bool MacProtocol::isProtocolFrameValueVariable() const +{ + if ((data.dst_mac_mode() != OstProto::Mac::e_mm_fixed) || + (data.src_mac_mode() != OstProto::Mac::e_mm_fixed)) + return true; + else + return false; +} + +int MacProtocol::protocolFrameVariableCount() const +{ + int count = 1; + + if (data.dst_mac_mode() != OstProto::Mac::e_mm_fixed) + count = AbstractProtocol::lcm(count, data.dst_mac_count()); + + if (data.src_mac_mode() != OstProto::Mac::e_mm_fixed) + count = AbstractProtocol::lcm(count, data.src_mac_count()); + + return count; +} + diff --git a/common/mac.h b/common/mac.h new file mode 100644 index 0000000..85e0ad5 --- /dev/null +++ b/common/mac.h @@ -0,0 +1,73 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _MAC_H +#define _MAC_H + +#include "abstractprotocol.h" + +#include "mac.pb.h" + +class MacProtocol : public AbstractProtocol +{ +public: + enum macfield + { + mac_dstAddr = 0, + mac_srcAddr, + + mac_dstMacMode, + mac_dstMacCount, + mac_dstMacStep, + mac_srcMacMode, + mac_srcMacCount, + mac_srcMacStep, + + mac_fieldCount + }; + + MacProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~MacProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual bool isProtocolFrameValueVariable() const; + virtual int protocolFrameVariableCount() const; + +private: + OstProto::Mac data; +}; + +#endif diff --git a/common/mac.proto b/common/mac.proto new file mode 100644 index 0000000..2055223 --- /dev/null +++ b/common/mac.proto @@ -0,0 +1,48 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Ethernet +message Mac { + + enum MacAddrMode { + e_mm_fixed = 0; + e_mm_inc = 1; + e_mm_dec = 2; + } + + // Dst Mac + optional uint64 dst_mac = 1; + optional MacAddrMode dst_mac_mode = 2 [default = e_mm_fixed]; + optional uint32 dst_mac_count = 3 [default = 16]; + optional uint32 dst_mac_step = 4 [default = 1]; + + // Src Mac + optional uint64 src_mac = 5; + optional MacAddrMode src_mac_mode = 6 [default = e_mm_fixed]; + optional uint32 src_mac_count = 7 [default = 16]; + optional uint32 src_mac_step = 8 [default = 1]; +} + +extend Protocol { + optional Mac mac = 100; +} diff --git a/common/mac.ui b/common/mac.ui new file mode 100644 index 0000000..821cf00 --- /dev/null +++ b/common/mac.ui @@ -0,0 +1,188 @@ + + mac + + + + 0 + 0 + 391 + 116 + + + + Form + + + + + + Address + + + + + + + Mode + + + + + + + Count + + + + + + + Step + + + + + + + Destination + + + + + + + + 120 + 0 + + + + >HH HH HH HH HH HH; + + + + + + + + + + + Fixed + + + + + Increment + + + + + Decrement + + + + + + + + false + + + + + + 0 + + + + + + + false + + + + + + 0 + + + + + + + Source + + + + + + + >HH HH HH HH HH HH; + + + + + + + + + + + Fixed + + + + + Increment + + + + + Decrement + + + + + + + + false + + + + + + + + + + false + + + + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/common/macconfig.cpp b/common/macconfig.cpp new file mode 100644 index 0000000..f17c140 --- /dev/null +++ b/common/macconfig.cpp @@ -0,0 +1,148 @@ +/* +Copyright (C) 2010,2013-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "macconfig.h" +#include "mac.h" + +#define MAX_MAC_ITER_COUNT 256 + +MacConfigForm::MacConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + QRegExp reMac("([0-9,a-f,A-F]{2,2}[:-]){5,5}[0-9,a-f,A-F]{2,2}"); + + setupUi(this); + leDstMac->setValidator(new QRegExpValidator(reMac, this)); + leSrcMac->setValidator(new QRegExpValidator(reMac, this)); + leDstMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this)); + leSrcMacCount->setValidator(new QIntValidator(1, MAX_MAC_ITER_COUNT, this)); +} + +MacConfigForm::~MacConfigForm() +{ +} + +MacConfigForm* MacConfigForm::createInstance() +{ + MacConfigForm *f = new MacConfigForm; + return f; +} + +void MacConfigForm::on_cmbDstMacMode_currentIndexChanged(int index) +{ + if (index == OstProto::Mac::e_mm_fixed) + { + leDstMacCount->setEnabled(false); + leDstMacStep->setEnabled(false); + } + else + { + leDstMacCount->setEnabled(true); + leDstMacStep->setEnabled(true); + } +} + +void MacConfigForm::on_cmbSrcMacMode_currentIndexChanged(int index) +{ + if (index == OstProto::Mac::e_mm_fixed) + { + leSrcMacCount->setEnabled(false); + leSrcMacStep->setEnabled(false); + } + else + { + leSrcMacCount->setEnabled(true); + leSrcMacStep->setEnabled(true); + } +} + +void MacConfigForm::loadWidget(AbstractProtocol *proto) +{ + leDstMac->setText( + proto->fieldData( + MacProtocol::mac_dstAddr, + AbstractProtocol::FieldTextValue + ).toString()); + cmbDstMacMode->setCurrentIndex( + proto->fieldData( + MacProtocol::mac_dstMacMode, + AbstractProtocol::FieldValue + ).toUInt()); + leDstMacCount->setText( + proto->fieldData( + MacProtocol::mac_dstMacCount, + AbstractProtocol::FieldValue + ).toString()); + leDstMacStep->setText( + proto->fieldData( + MacProtocol::mac_dstMacStep, + AbstractProtocol::FieldValue + ).toString()); + + leSrcMac->setText( + proto->fieldData( + MacProtocol::mac_srcAddr, + AbstractProtocol::FieldTextValue + ).toString()); + cmbSrcMacMode->setCurrentIndex( + proto->fieldData( + MacProtocol::mac_srcMacMode, + AbstractProtocol::FieldValue + ).toUInt()); + leSrcMacCount->setText( + proto->fieldData( + MacProtocol::mac_srcMacCount, + AbstractProtocol::FieldValue + ).toString()); + leSrcMacStep->setText( + proto->fieldData( + MacProtocol::mac_srcMacStep, + AbstractProtocol::FieldValue + ).toString()); +} + +void MacConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + MacProtocol::mac_dstAddr, + leDstMac->text().remove(QChar(' '))); + proto->setFieldData( + MacProtocol::mac_dstMacMode, + cmbDstMacMode->currentIndex()); + proto->setFieldData( + MacProtocol::mac_dstMacCount, + leDstMacCount->text()); + proto->setFieldData( + MacProtocol::mac_dstMacStep, + leDstMacStep->text()); + + proto->setFieldData( + MacProtocol::mac_srcAddr, + leSrcMac->text().remove(QChar(' '))); + proto->setFieldData( + MacProtocol::mac_srcMacMode, + cmbSrcMacMode->currentIndex()); + proto->setFieldData( + MacProtocol::mac_srcMacCount, + leSrcMacCount->text()); + proto->setFieldData( + MacProtocol::mac_srcMacStep, + leSrcMacStep->text()); +} + diff --git a/common/macconfig.h b/common/macconfig.h new file mode 100644 index 0000000..952c64e --- /dev/null +++ b/common/macconfig.h @@ -0,0 +1,45 @@ +/* +Copyright (C) 2010-2012 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _MAC_CONFIG_H +#define _MAC_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_mac.h" + +class MacConfigForm : + public AbstractProtocolConfigForm, + private Ui::mac +{ + Q_OBJECT +public: + MacConfigForm(QWidget *parent = 0); + virtual ~MacConfigForm(); + + static MacConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private slots: + void on_cmbDstMacMode_currentIndexChanged(int index); + void on_cmbSrcMacMode_currentIndexChanged(int index); +}; + +#endif diff --git a/common/mld.cpp b/common/mld.cpp new file mode 100644 index 0000000..52b48a5 --- /dev/null +++ b/common/mld.cpp @@ -0,0 +1,543 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "mld.h" + +#include "iputils.h" + +#include +#include + +MldProtocol::MldProtocol(StreamBase *stream, AbstractProtocol *parent) + : GmpProtocol(stream, parent) +{ + _hasPayload = false; + + data.set_type(kMldV1Query); +} + +MldProtocol::~MldProtocol() +{ +} + +AbstractProtocol* MldProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new MldProtocol(stream, parent); +} + +quint32 MldProtocol::protocolNumber() const +{ + return OstProto::Protocol::kMldFieldNumber; +} + +void MldProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::mld)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void MldProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::mld)) + data.MergeFrom(protocol.GetExtension(OstProto::mld)); +} + +QString MldProtocol::name() const +{ + return QString("Multicast Listener Discovery"); +} + +QString MldProtocol::shortName() const +{ + return QString("MLD"); +} + +quint32 MldProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdIp: return 0x3a; + default:break; + } + + return AbstractProtocol::protocolId(type); +} + +AbstractProtocol::FieldFlags MldProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = GmpProtocol::fieldFlags(index); + + switch(index) + { + case kMldMrt: + case kMldRsvd: + if (msgType() != kMldV2Report) + flags |= FrameField; + break; + default: + break; + } + + return flags; +} + +QVariant MldProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case kRsvdMrtCode: + { + switch(attrib) + { + case FieldName: return QString("Code"); + default: break; + } + break; + } + + case kMldMrt: + { + quint16 mrt = 0, mrcode = 0; + + if (msgType() == kMldV2Query) + { + mrt = data.max_response_time(); + mrcode = mrc(mrt); + } + else if (msgType() == kMldV1Query) + mrcode = mrt = data.max_response_time() & 0xFFFF; + + switch(attrib) + { + case FieldName: + if (isQuery()) + return QString("Max Response Time"); + return QString("Reserved"); + case FieldValue: + return mrt; + case FieldTextValue: + return QString("%1 ms").arg(mrt); + case FieldFrameValue: + { + QByteArray fv; + + fv.resize(2); + qToBigEndian(mrcode, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case kMldRsvd: + { + quint16 rsvd = 0; + + switch(attrib) + { + case FieldName: + return QString("Reserved"); + case FieldValue: + return rsvd; + case FieldTextValue: + return QString("%1").arg(rsvd); + case FieldFrameValue: + { + QByteArray fv; + + fv.resize(2); + qToBigEndian(rsvd, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case kGroupAddress: + { + quint64 grpHi = 0, grpLo = 0; + + ipUtils::ipAddress( + data.group_address().v6_hi(), + data.group_address().v6_lo(), + data.group_prefix(), + ipUtils::AddrMode(data.group_mode()), + data.group_count(), + streamIndex, + grpHi, + grpLo); + + switch(attrib) + { + case FieldName: + return QString("Group Address"); + case FieldValue: + case FieldTextValue: + case FieldFrameValue: + { + QByteArray fv; + fv.resize(16); + qToBigEndian(grpHi, (uchar*) fv.data()); + qToBigEndian(grpLo, (uchar*) (fv.data() + 8)); + if (attrib == FieldFrameValue) + return fv; + else + return QHostAddress((quint8*)fv.constData()).toString(); + } + default: + break; + } + break; + } + case kSources: + { + switch(attrib) + { + case FieldName: + return QString("Source List"); + case FieldValue: + { + QStringList list; + QByteArray fv; + fv.resize(16); + for (int i = 0; i < data.sources_size(); i++) + { + qToBigEndian(data.sources(i).v6_hi(), + (uchar*)fv.data()); + qToBigEndian(data.sources(i).v6_lo(), + (uchar*)fv.data()+8); + + list << QHostAddress((quint8*)fv.constData()).toString(); + } + return list; + } + case FieldFrameValue: + { + QByteArray fv; + fv.resize(16 * data.sources_size()); + for (int i = 0; i < data.sources_size(); i++) + { + qToBigEndian(data.sources(i).v6_hi(), + (uchar*)(fv.data() + i*16)); + qToBigEndian(data.sources(i).v6_lo(), + (uchar*)(fv.data() + i*16 + 8)); + } + return fv; + } + case FieldTextValue: + { + QStringList list; + QByteArray fv; + fv.resize(16); + for (int i = 0; i < data.sources_size(); i++) + { + qToBigEndian(data.sources(i).v6_hi(), + (uchar*)fv.data()); + qToBigEndian(data.sources(i).v6_lo(), + (uchar*)fv.data()+8); + + list << QHostAddress((quint8*)fv.constData()).toString(); + } + return list.join(", "); + } + default: + break; + } + break; + } + case kGroupRecords: + { + switch(attrib) + { + case FieldValue: + { + QVariantList grpRecords = GmpProtocol::fieldData( + index, attrib, streamIndex).toList(); + QByteArray ip; + + ip.resize(16); + + for (int i = 0; i < data.group_records_size(); i++) + { + QVariantMap grpRec = grpRecords.at(i).toMap(); + OstProto::Gmp::GroupRecord rec = data.group_records(i); + + qToBigEndian(quint64(rec.group_address().v6_hi()), + (uchar*)(ip.data())); + qToBigEndian(quint64(rec.group_address().v6_lo()), + (uchar*)(ip.data() + 8)); + grpRec["groupRecordAddress"] = QHostAddress( + (quint8*)ip.constData()).toString(); + + QStringList sl; + for (int j = 0; j < rec.sources_size(); j++) + { + qToBigEndian(rec.sources(j).v6_hi(), + (uchar*)(ip.data())); + qToBigEndian(rec.sources(j).v6_lo(), + (uchar*)(ip.data() + 8)); + sl.append(QHostAddress( + (quint8*)ip.constData()).toString()); + } + grpRec["groupRecordSourceList"] = sl; + + grpRecords.replace(i, grpRec); + } + return grpRecords; + } + case FieldFrameValue: + { + QVariantList list = GmpProtocol::fieldData( + index, attrib, streamIndex).toList(); + QByteArray fv; + QByteArray ip; + ip.resize(16); + + for (int i = 0; i < data.group_records_size(); i++) + { + OstProto::Gmp::GroupRecord rec = data.group_records(i); + QByteArray rv = list.at(i).toByteArray(); + + rv.insert(4, QByteArray(16+16*rec.sources_size(), char(0))); + qToBigEndian(rec.group_address().v6_hi(), + (uchar*)(rv.data()+4)); + qToBigEndian(rec.group_address().v6_lo(), + (uchar*)(rv.data()+4+8)); + for (int j = 0; j < rec.sources_size(); j++) + { + qToBigEndian(rec.sources(j).v6_hi(), + (uchar*)(rv.data()+20+16*j)); + qToBigEndian(rec.sources(j).v6_lo(), + (uchar*)(rv.data()+20+16*j+8)); + } + + fv.append(rv); + } + return fv; + } + case FieldTextValue: + { + QStringList list = GmpProtocol::fieldData( + index, attrib, streamIndex).toStringList(); + QByteArray ip; + + ip.resize(16); + + for (int i = 0; i < data.group_records_size(); i++) + { + OstProto::Gmp::GroupRecord rec = data.group_records(i); + QString recStr = list.at(i); + QString str; + + qToBigEndian(rec.group_address().v6_hi(), + (uchar*)(ip.data())); + qToBigEndian(rec.group_address().v6_lo(), + (uchar*)(ip.data() + 8)); + str.append(QString("Group: %1").arg( + QHostAddress((quint8*)ip.constData()).toString())); + + str.append("; Sources: "); + QStringList sl; + for (int j = 0; j < rec.sources_size(); j++) + { + qToBigEndian(rec.sources(j).v6_hi(), + (uchar*)(ip.data())); + qToBigEndian(rec.sources(j).v6_lo(), + (uchar*)(ip.data() + 8)); + sl.append(QHostAddress( + (quint8*)ip.constData()).toString()); + } + str.append(sl.join(", ")); + + recStr.replace("XXX", str); + list.replace(i, recStr); + } + return list.join("\n").insert(0, "\n"); + } + default: + break; + } + break; + } + default: + break; + } + + return GmpProtocol::fieldData(index, attrib, streamIndex); +} + +bool MldProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case kGroupAddress: + { + Q_IPV6ADDR addr = QHostAddress(value.toString()).toIPv6Address(); + quint64 x; + + x = (quint64(addr[0]) << 56) + | (quint64(addr[1]) << 48) + | (quint64(addr[2]) << 40) + | (quint64(addr[3]) << 32) + | (quint64(addr[4]) << 24) + | (quint64(addr[5]) << 16) + | (quint64(addr[6]) << 8) + | (quint64(addr[7]) << 0); + data.mutable_group_address()->set_v6_hi(x); + + x = (quint64(addr[ 8]) << 56) + | (quint64(addr[ 9]) << 48) + | (quint64(addr[10]) << 40) + | (quint64(addr[11]) << 32) + | (quint64(addr[12]) << 24) + | (quint64(addr[13]) << 16) + | (quint64(addr[14]) << 8) + | (quint64(addr[15]) << 0); + data.mutable_group_address()->set_v6_lo(x); + break; + } + + case kSources: + { + QStringList list = value.toStringList(); + + data.clear_sources(); + foreach(QString str, list) + { + OstProto::Gmp::IpAddress *src = data.add_sources(); + Q_IPV6ADDR addr = QHostAddress(str).toIPv6Address(); + quint64 x; + + x = (quint64(addr[0]) << 56) + | (quint64(addr[1]) << 48) + | (quint64(addr[2]) << 40) + | (quint64(addr[3]) << 32) + | (quint64(addr[4]) << 24) + | (quint64(addr[5]) << 16) + | (quint64(addr[6]) << 8) + | (quint64(addr[7]) << 0); + src->set_v6_hi(x); + + x = (quint64(addr[ 8]) << 56) + | (quint64(addr[ 9]) << 48) + | (quint64(addr[10]) << 40) + | (quint64(addr[11]) << 32) + | (quint64(addr[12]) << 24) + | (quint64(addr[13]) << 16) + | (quint64(addr[14]) << 8) + | (quint64(addr[15]) << 0); + src->set_v6_lo(x); + } + break; + } + + case kGroupRecords: + { + GmpProtocol::setFieldData(index, value, attrib); + QVariantList list = value.toList(); + + for (int i = 0; i < list.count(); i++) + { + QVariantMap grpRec = list.at(i).toMap(); + OstProto::Gmp::GroupRecord *rec = data.mutable_group_records(i); + Q_IPV6ADDR addr = QHostAddress( + grpRec["groupRecordAddress"].toString()) + .toIPv6Address(); + quint64 x; + + x = (quint64(addr[0]) << 56) + | (quint64(addr[1]) << 48) + | (quint64(addr[2]) << 40) + | (quint64(addr[3]) << 32) + | (quint64(addr[4]) << 24) + | (quint64(addr[5]) << 16) + | (quint64(addr[6]) << 8) + | (quint64(addr[7]) << 0); + rec->mutable_group_address()->set_v6_hi(x); + + x = (quint64(addr[ 8]) << 56) + | (quint64(addr[ 9]) << 48) + | (quint64(addr[10]) << 40) + | (quint64(addr[11]) << 32) + | (quint64(addr[12]) << 24) + | (quint64(addr[13]) << 16) + | (quint64(addr[14]) << 8) + | (quint64(addr[15]) << 0); + rec->mutable_group_address()->set_v6_lo(x); + + QStringList srcList = grpRec["groupRecordSourceList"] + .toStringList(); + rec->clear_sources(); + foreach (QString str, srcList) + { + OstProto::Gmp::IpAddress *src = rec->add_sources(); + Q_IPV6ADDR addr = QHostAddress(str).toIPv6Address(); + quint64 x; + + x = (quint64(addr[0]) << 56) + | (quint64(addr[1]) << 48) + | (quint64(addr[2]) << 40) + | (quint64(addr[3]) << 32) + | (quint64(addr[4]) << 24) + | (quint64(addr[5]) << 16) + | (quint64(addr[6]) << 8) + | (quint64(addr[7]) << 0); + src->set_v6_hi(x); + + x = (quint64(addr[ 8]) << 56) + | (quint64(addr[ 9]) << 48) + | (quint64(addr[10]) << 40) + | (quint64(addr[11]) << 32) + | (quint64(addr[12]) << 24) + | (quint64(addr[13]) << 16) + | (quint64(addr[14]) << 8) + | (quint64(addr[15]) << 0); + src->set_v6_lo(x); + } + } + + break; + } + + default: + isOk = GmpProtocol::setFieldData(index, value, attrib); + break; + } + +_exit: + return isOk; +} + +quint16 MldProtocol::checksum(int streamIndex) const +{ + return AbstractProtocol::protocolFrameCksum(streamIndex, CksumTcpUdp); +} diff --git a/common/mld.h b/common/mld.h new file mode 100644 index 0000000..5403af6 --- /dev/null +++ b/common/mld.h @@ -0,0 +1,95 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _MLD_H +#define _MLD_H + +#include "gmp.h" +#include "mld.pb.h" + +// MLD uses the same msg type value for 'Query' messages across +// versions despite the fields being different. To distinguish +// Query messages of different versions, we use an additional +// upper byte +enum MldMsgType +{ + kMldV1Query = 0x82, + kMldV1Report = 0x83, + kMldV1Done = 0x84, + + kMldV2Query = 0xFF82, + kMldV2Report = 0x8F +}; + +class MldProtocol : public GmpProtocol +{ +public: + MldProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~MldProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual QString name() const; + virtual QString shortName() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + +protected: + virtual bool isSsmReport() const; + virtual bool isQuery() const; + virtual bool isSsmQuery() const; + + virtual quint16 checksum(int streamIndex) const; + +private: + int mrc(int value) const; +}; + +inline bool MldProtocol::isSsmReport() const +{ + return (msgType() == kMldV2Report); +} + +inline bool MldProtocol::isQuery() const +{ + return ((msgType() == kMldV1Query) + || (msgType() == kMldV2Query)); +} + +inline bool MldProtocol::isSsmQuery() const +{ + return (msgType() == kMldV2Query); +} + +inline int MldProtocol::mrc(int value) const +{ + return quint16(value); // TODO: if value > 128, convert to mantissa/exp form +} + +#endif diff --git a/common/mld.proto b/common/mld.proto new file mode 100755 index 0000000..2f491e8 --- /dev/null +++ b/common/mld.proto @@ -0,0 +1,27 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; +import "gmp.proto"; + +package OstProto; + +extend Protocol { + optional Gmp mld = 404; +} diff --git a/common/mldconfig.cpp b/common/mldconfig.cpp new file mode 100644 index 0000000..bc871c3 --- /dev/null +++ b/common/mldconfig.cpp @@ -0,0 +1,109 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "mldconfig.h" +#include "mld.h" + +#include "ipv6addressdelegate.h" +#include "ipv6addressvalidator.h" + +MldConfigForm::MldConfigForm(QWidget *parent) + : GmpConfigForm(parent) +{ + connect(msgTypeCombo, SIGNAL(currentIndexChanged(int)), + SLOT(on_msgTypeCombo_currentIndexChanged(int))); + + msgTypeCombo->setValueMask(0xFF); + msgTypeCombo->addItem(kMldV1Query, "MLDv1 Query"); + msgTypeCombo->addItem(kMldV1Report, "MLDv1 Report"); + msgTypeCombo->addItem(kMldV1Done, "MLDv1 Done"); + msgTypeCombo->addItem(kMldV2Query, "MLDv2 Query"); + msgTypeCombo->addItem(kMldV2Report, "MLDv2 Report"); + + _defaultGroupIp = "::"; + _defaultSourceIp = "::"; + + groupAddress->setValidator(new IPv6AddressValidator(this)); + groupRecordAddress->setValidator(new IPv6AddressValidator(this)); + sourceList->setItemDelegate(new IPv6AddressDelegate(this)); + groupRecordSourceList->setItemDelegate(new IPv6AddressDelegate(this)); +} + +MldConfigForm::~MldConfigForm() +{ +} + +MldConfigForm* MldConfigForm::createInstance() +{ + return new MldConfigForm; +} + +void MldConfigForm::loadWidget(AbstractProtocol *proto) +{ + GmpConfigForm::loadWidget(proto); + + maxResponseTime->setText( + proto->fieldData( + MldProtocol::kMldMrt, + AbstractProtocol::FieldValue + ).toString()); +} + +void MldConfigForm::storeWidget(AbstractProtocol *proto) +{ + GmpConfigForm::storeWidget(proto); + + proto->setFieldData( + MldProtocol::kMldMrt, + maxResponseTime->text()); +} + +// +// -- private slots +// + +void MldConfigForm::on_msgTypeCombo_currentIndexChanged(int /*index*/) +{ + switch(msgTypeCombo->currentValue()) + { + case kMldV1Query: + case kMldV1Report: + case kMldV1Done: + asmGroup->show(); + ssmWidget->hide(); + break; + + case kMldV2Query: + asmGroup->show(); + ssmWidget->setCurrentIndex(kSsmQueryPage); + ssmWidget->show(); + break; + + case kMldV2Report: + asmGroup->hide(); + ssmWidget->setCurrentIndex(kSsmReportPage); + ssmWidget->show(); + break; + + default: + asmGroup->hide(); + ssmWidget->hide(); + break; + } +} diff --git a/common/mldconfig.h b/common/mldconfig.h new file mode 100644 index 0000000..b25617a --- /dev/null +++ b/common/mldconfig.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _MLD_CONFIG_H +#define _MLD_CONFIG_H + +#include "gmpconfig.h" + +class MldConfigForm : public GmpConfigForm +{ + Q_OBJECT +public: + MldConfigForm(QWidget *parent = 0); + + virtual ~MldConfigForm(); + + static MldConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private slots: + void on_msgTypeCombo_currentIndexChanged(int index); +}; + +#endif diff --git a/common/mldpdml.cpp b/common/mldpdml.cpp new file mode 100644 index 0000000..b17503e --- /dev/null +++ b/common/mldpdml.cpp @@ -0,0 +1,133 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "mldpdml.h" + +#include "mld.pb.h" + +PdmlMldProtocol::PdmlMldProtocol() +{ + ostProtoId_ = OstProto::Protocol::kMldFieldNumber; + + fieldMap_.insert("icmpv6.code", OstProto::Gmp::kRsvdCodeFieldNumber); + fieldMap_.insert("icmpv6.checksum", OstProto::Gmp::kChecksumFieldNumber); + fieldMap_.insert("icmpv6.mld.maximum_response_delay", + OstProto::Gmp::kMaxResponseTimeFieldNumber); // FIXME + + fieldMap_.insert("icmpv6.mld.flag.s", OstProto::Gmp::kSFlagFieldNumber); + fieldMap_.insert("icmpv6.mld.flag.qrv", OstProto::Gmp::kQrvFieldNumber); + fieldMap_.insert("icmpv6.mld.qqi", OstProto::Gmp::kQqiFieldNumber); // FIXME + fieldMap_.insert("icmpv6.mld.nb_sources", + OstProto::Gmp::kSourceCountFieldNumber); + + fieldMap_.insert("icmpv6.mldr.nb_mcast_records", + OstProto::Gmp::kGroupRecordCountFieldNumber); +} + +PdmlProtocol* PdmlMldProtocol::createInstance() +{ + return new PdmlMldProtocol(); +} + +void PdmlMldProtocol::preProtocolHandler(QString /*name*/, + const QXmlStreamAttributes &attributes, int /*expectedPos*/, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + bool isOk; + OstProto::Gmp *mld = pbProto->MutableExtension(OstProto::mld); + + mld->set_is_override_rsvd_code(true); + mld->set_is_override_checksum(true); + mld->set_is_override_source_count(true); + mld->set_is_override_group_record_count(true); + + protoSize_ = attributes.value("size").toString().toUInt(&isOk); +} + +void PdmlMldProtocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + bool isOk; + OstProto::Gmp *mld = pbProto->MutableExtension(OstProto::mld); + QString valueHexStr = attributes.value("value").toString(); + + if (name == "icmpv6.type") + { + uint type = valueHexStr.toUInt(&isOk, kBaseHex); + + if ((type == kMldQuery) && (protoSize_ >= 28)) + type = kMldV2Query; + + mld->set_type(type); + } + else if (name == "icmpv6.mld.multicast_address") + { + mld->mutable_group_address()->set_v6_hi( + valueHexStr.left(16).toULongLong(&isOk, kBaseHex)); + mld->mutable_group_address()->set_v6_lo( + valueHexStr.right(16).toULongLong(&isOk, kBaseHex)); + } + else if (name == "icmpv6.mld.source_address") + { + OstProto::Gmp::IpAddress *ip = mld->add_sources(); + ip->set_v6_hi(valueHexStr.left(16).toULongLong(&isOk, kBaseHex)); + ip->set_v6_lo(valueHexStr.right(16).toULongLong(&isOk, kBaseHex)); + } + else if (name == "icmpv6.mldr.mar.record_type") + { + OstProto::Gmp::GroupRecord *rec = mld->add_group_records(); + rec->set_type(OstProto::Gmp::GroupRecord::RecordType( + valueHexStr.toUInt(&isOk, kBaseHex))); + rec->set_is_override_source_count(true); + rec->set_is_override_aux_data_length(true); + } + else if (name == "icmpv6.mldr.mar.aux_data_len") + { + mld->mutable_group_records(mld->group_records_size() - 1)-> + set_aux_data_length(valueHexStr.toUInt(&isOk, kBaseHex)); + } + else if (name == "icmpv6.mldr.mar.nb_sources") + { + mld->mutable_group_records(mld->group_records_size() - 1)-> + set_source_count(valueHexStr.toUInt(&isOk, kBaseHex)); + } + else if (name == "icmpv6.mldr.mar.multicast_address") + { + OstProto::Gmp::IpAddress *ip = mld->mutable_group_records( + mld->group_records_size() - 1)->mutable_group_address(); + ip->set_v6_hi(valueHexStr.left(16).toULongLong(&isOk, kBaseHex)); + ip->set_v6_lo(valueHexStr.right(16).toULongLong(&isOk, kBaseHex)); + } + else if (name == "icmpv6.mldr.mar.source_address") + { + OstProto::Gmp::IpAddress *ip = mld->mutable_group_records( + mld->group_records_size() - 1)->add_sources(); + ip->set_v6_hi(valueHexStr.left(16).toULongLong(&isOk, kBaseHex)); + ip->set_v6_lo(valueHexStr.right(16).toULongLong(&isOk, kBaseHex)); + } + else if (name == "icmpv6.mldr.mar.auxiliary_data") + { + QByteArray ba = QByteArray::fromHex( + attributes.value("value").toString().toUtf8()); + mld->mutable_group_records(mld->group_records_size() - 1)-> + set_aux_data(ba.constData(), ba.size()); + } +} + diff --git a/common/mldpdml.h b/common/mldpdml.h new file mode 100644 index 0000000..0c47fbe --- /dev/null +++ b/common/mldpdml.h @@ -0,0 +1,47 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _MLD_PDML_H +#define _MLD_PDML_H + +#include "pdmlprotocol.h" + +class PdmlMldProtocol : public PdmlProtocol +{ + friend class PdmlIcmp6Protocol; +public: + static PdmlProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); +protected: + PdmlMldProtocol(); +private: + static const uint kMldQuery = 0x82; + static const uint kMldV1Query = 0x82; + static const uint kMldV2Query = 0xFF82; + + uint protoSize_; +}; + +#endif diff --git a/common/ostproto.pro b/common/ostproto.pro new file mode 100644 index 0000000..e4467dc --- /dev/null +++ b/common/ostproto.pro @@ -0,0 +1,114 @@ +TEMPLATE = lib +CONFIG += qt staticlib +QT -= gui +QT += network script +LIBS += \ + -lprotobuf + +PROTOS = \ + protocol.proto \ + mac.proto \ + payload.proto \ + eth2.proto \ + dot3.proto \ + llc.proto \ + snap.proto \ + dot2llc.proto \ + dot2snap.proto \ + vlan.proto \ + svlan.proto \ + vlanstack.proto \ + arp.proto \ + ip4.proto \ + ip6.proto \ + ip6over4.proto \ + ip4over6.proto \ + ip4over4.proto \ + ip6over6.proto \ + icmp.proto \ + gmp.proto \ + igmp.proto \ + mld.proto \ + tcp.proto \ + udp.proto \ + textproto.proto \ + userscript.proto \ + hexdump.proto \ + sample.proto + +HEADERS = \ + abstractprotocol.h \ + comboprotocol.h \ + protocolmanager.h \ + protocollist.h \ + protocollistiterator.h \ + streambase.h \ + +HEADERS += \ + mac.h \ + vlan.h \ + svlan.h \ + vlanstack.h \ + eth2.h \ + dot3.h \ + llc.h \ + dot2llc.h \ + snap.h \ + dot2snap.h \ + arp.h \ + ip4.h \ + ip6.h \ + ip4over4.h \ + ip4over6.h \ + ip6over4.h \ + ip6over6.h \ + gmp.h \ + icmp.h \ + igmp.h \ + mld.h \ + tcp.h \ + udp.h \ + textproto.h \ + hexdump.h \ + payload.h \ + sample.h \ + userscript.h + +SOURCES = \ + abstractprotocol.cpp \ + crc32c.cpp \ + protocolmanager.cpp \ + protocollist.cpp \ + protocollistiterator.cpp \ + streambase.cpp \ + +SOURCES += \ + mac.cpp \ + vlan.cpp \ + svlan.cpp \ + eth2.cpp \ + dot3.cpp \ + llc.cpp \ + snap.cpp \ + arp.cpp \ + ip4.cpp \ + ip6.cpp \ + gmp.cpp \ + icmp.cpp \ + igmp.cpp \ + mld.cpp \ + tcp.cpp \ + udp.cpp \ + textproto.cpp \ + hexdump.cpp \ + payload.cpp \ + sample.cpp \ + userscript.cpp + +QMAKE_DISTCLEAN += object_script.* + +#binding.depends = compiler_protobuf_py_make_all +#QMAKE_EXTRA_TARGETS += binding + +include(../protobuf.pri) + diff --git a/common/ostprotogui.pro b/common/ostprotogui.pro new file mode 100644 index 0000000..dcbec0c --- /dev/null +++ b/common/ostprotogui.pro @@ -0,0 +1,131 @@ +TEMPLATE = lib +CONFIG += qt staticlib +QT += network xml script +INCLUDEPATH += "../extra/qhexedit2/src" +LIBS += \ + -lprotobuf + +FORMS = \ + pcapfileimport.ui \ + +FORMS += \ + mac.ui \ + vlan.ui \ + eth2.ui \ + dot3.ui \ + llc.ui \ + snap.ui \ + arp.ui \ + ip4.ui \ + ip6.ui \ + gmp.ui \ + icmp.ui \ + tcp.ui \ + udp.ui \ + textproto.ui \ + hexdump.ui \ + payload.ui \ + sample.ui \ + userscript.ui + +PROTOS = \ + fileformat.proto + +# TODO: Move fileformat related stuff into a different library - why? +HEADERS = \ + ostprotolib.h \ + abstractfileformat.h \ + fileformat.h \ + ipv4addressdelegate.h \ + ipv6addressdelegate.h \ + pcapfileformat.h \ + pdmlfileformat.h \ + pythonfileformat.h \ + pdmlprotocol.h \ + pdmlprotocols.h \ + pdmlreader.h + +HEADERS += \ + abstractprotocolconfig.h \ + comboprotocolconfig.h \ + protocolwidgetfactory.h \ + macconfig.h \ + vlanconfig.h \ + svlanconfig.h \ + vlanstackconfig.h \ + eth2config.h \ + dot3config.h \ + llcconfig.h \ + dot2llcconfig.h \ + snapconfig.h \ + dot2snapconfig.h \ + arpconfig.h \ + ip4config.h \ + ip6config.h \ + ip4over4config.h \ + gmpconfig.h \ + icmpconfig.h \ + igmpconfig.h \ + mldconfig.h \ + tcpconfig.h \ + udpconfig.h \ + textprotoconfig.h \ + hexdumpconfig.h \ + payloadconfig.h \ + sampleconfig.h \ + userscriptconfig.h + +SOURCES += \ + ostprotolib.cpp \ + abstractfileformat.cpp \ + fileformat.cpp \ + pcapfileformat.cpp \ + pdmlfileformat.cpp \ + pythonfileformat.cpp \ + pdmlprotocol.cpp \ + pdmlprotocols.cpp \ + pdmlreader.cpp \ + +SOURCES += \ + protocolwidgetfactory.cpp \ + macconfig.cpp \ + vlanconfig.cpp \ + eth2config.cpp \ + dot3config.cpp \ + llcconfig.cpp \ + snapconfig.cpp \ + arpconfig.cpp \ + ip4config.cpp \ + ip6config.cpp \ + gmpconfig.cpp \ + icmpconfig.cpp \ + igmpconfig.cpp \ + mldconfig.cpp \ + tcpconfig.cpp \ + udpconfig.cpp \ + textprotoconfig.cpp \ + hexdumpconfig.cpp \ + payloadconfig.cpp \ + sampleconfig.cpp \ + userscriptconfig.cpp + +SOURCES += \ + vlanpdml.cpp \ + svlanpdml.cpp \ + eth2pdml.cpp \ + llcpdml.cpp \ + arppdml.cpp \ + ip4pdml.cpp \ + ip6pdml.cpp \ + icmppdml.cpp \ + icmp6pdml.cpp \ + igmppdml.cpp \ + mldpdml.cpp \ + tcppdml.cpp \ + udppdml.cpp \ + textprotopdml.cpp \ + samplepdml.cpp + +QMAKE_DISTCLEAN += object_script.* + +include(../protobuf.pri) diff --git a/common/ostprotolib.cpp b/common/ostprotolib.cpp new file mode 100644 index 0000000..bd1fcc2 --- /dev/null +++ b/common/ostprotolib.cpp @@ -0,0 +1,55 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "ostprotolib.h" + +QString OstProtoLib::tsharkPath_; +QString OstProtoLib::gzipPath_; +QString OstProtoLib::diffPath_; +QString OstProtoLib::awkPath_; + +// TODO: one set method for each external app +void OstProtoLib::setExternalApplicationPaths(QString tsharkPath, + QString gzipPath, QString diffPath, QString awkPath) +{ + tsharkPath_ = tsharkPath; + gzipPath_ = gzipPath; + diffPath_ = diffPath; + awkPath_ = awkPath; +} + +QString OstProtoLib::tsharkPath() +{ + return tsharkPath_; +} + +QString OstProtoLib::gzipPath() +{ + return gzipPath_; +} + +QString OstProtoLib::diffPath() +{ + return diffPath_; +} + +QString OstProtoLib::awkPath() +{ + return awkPath_; +} diff --git a/common/ostprotolib.h b/common/ostprotolib.h new file mode 100644 index 0000000..4d10626 --- /dev/null +++ b/common/ostprotolib.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _OST_PROTO_LIB_H +#define _OST_PROTO_LIB_H + +#include + +class OstProtoLib +{ +public: + static void setExternalApplicationPaths(QString tsharkPath, + QString gzipPath, QString diffPath, QString awkPath); + + static QString tsharkPath(); + static QString gzipPath(); + static QString diffPath(); + static QString awkPath(); + +private: + static QString tsharkPath_; + static QString gzipPath_; + static QString diffPath_; + static QString awkPath_; +}; + +#endif diff --git a/common/payload.cpp b/common/payload.cpp new file mode 100644 index 0000000..2a7756d --- /dev/null +++ b/common/payload.cpp @@ -0,0 +1,258 @@ +/* +Copyright (C) 2010, 2013-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "payload.h" +#include "streambase.h" + +PayloadProtocol::PayloadProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +PayloadProtocol::~PayloadProtocol() +{ +} + +AbstractProtocol* PayloadProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new PayloadProtocol(stream, parent); +} + +quint32 PayloadProtocol::protocolNumber() const +{ + return OstProto::Protocol::kPayloadFieldNumber; +} + +void PayloadProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::payload)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void PayloadProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::payload)) + data.MergeFrom(protocol.GetExtension(OstProto::payload)); +} + +QString PayloadProtocol::name() const +{ + return QString("Payload Data"); +} + +QString PayloadProtocol::shortName() const +{ + return QString("DATA"); +} + +int PayloadProtocol::protocolFrameSize(int streamIndex) const +{ + int len; + + len = mpStream->frameLen(streamIndex) - protocolFrameOffset(streamIndex) + - kFcsSize; + + if (len < 0) + len = 0; + + qDebug("%s: this = %p, streamIndex = %d, len = %d", __FUNCTION__, this, + streamIndex, len); + return len; +} + +int PayloadProtocol::fieldCount() const +{ + return payload_fieldCount; +} + +AbstractProtocol::FieldFlags PayloadProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case payload_dataPattern: + break; + + // Meta fields + case payload_dataPatternMode: + flags &= ~FrameField; + flags |= MetaField; + break; + } + + return flags; +} + +QVariant PayloadProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case payload_dataPattern: + switch(attrib) + { + case FieldName: + return QString("Data"); + case FieldValue: + return data.pattern(); + case FieldTextValue: + return QString(fieldData(index, FieldFrameValue, + streamIndex).toByteArray().toHex()); + case FieldFrameValue: + { + QByteArray fv; + int dataLen; + + dataLen = protocolFrameSize(streamIndex); + + // FIXME: Hack! Bad! Bad! Very Bad!!! + if (dataLen <= 0) + dataLen = 1; + + fv.resize(dataLen+4); + switch(data.pattern_mode()) + { + case OstProto::Payload::e_dp_fixed_word: + for (int i = 0; i < (dataLen/4)+1; i++) + qToBigEndian((quint32) data.pattern(), + (uchar*)(fv.data()+(i*4)) ); + break; + case OstProto::Payload::e_dp_inc_byte: + for (int i = 0; i < dataLen; i++) + fv[i] = i % (0xFF + 1); + break; + case OstProto::Payload::e_dp_dec_byte: + for (int i = 0; i < dataLen; i++) + fv[i] = 0xFF - (i % (0xFF + 1)); + break; + case OstProto::Payload::e_dp_random: + //! \todo (HIGH) cksum is incorrect for random pattern + for (int i = 0; i < dataLen; i++) + fv[i] = qrand() % (0xFF + 1); + break; + default: + qWarning("Unhandled data pattern %d", + data.pattern_mode()); + } + fv.resize(dataLen); + return fv; + } + default: + break; + } + break; + + // Meta fields + + case payload_dataPatternMode: + switch(attrib) + { + case FieldValue: return data.pattern_mode(); + default: break; + } + break; + default: + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool PayloadProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + return false; + + switch (index) + { + case payload_dataPattern: + { + uint pattern = value.toUInt(&isOk); + if (isOk) + data.set_pattern(pattern); + break; + } + case payload_dataPatternMode: + { + uint mode = value.toUInt(&isOk); + if (isOk && data.DataPatternMode_IsValid(mode)) + data.set_pattern_mode(OstProto::Payload::DataPatternMode(mode)); + else + isOk = false; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + return isOk; +} + +bool PayloadProtocol::isProtocolFrameValueVariable() const +{ + if (isProtocolFrameSizeVariable() + || data.pattern_mode() == OstProto::Payload::e_dp_random) + return true; + else + return false; +} + +bool PayloadProtocol::isProtocolFrameSizeVariable() const +{ + if (mpStream->lenMode() == StreamBase::e_fl_fixed) + return false; + else + return true; +} + +int PayloadProtocol::protocolFrameVariableCount() const +{ + int count = 1; + + if (data.pattern_mode() == OstProto::Payload::e_dp_random) + { + switch(mpStream->sendUnit()) + { + case OstProto::StreamControl::e_su_packets: + return mpStream->numPackets(); + + case OstProto::StreamControl::e_su_bursts: + return int(mpStream->numBursts() + * mpStream->burstSize() + * mpStream->burstRate()); + } + } + + if (mpStream->lenMode() != StreamBase::e_fl_fixed) + { + count = AbstractProtocol::lcm(count, + mpStream->frameLenMax() - mpStream->frameLenMin() + 1); + } + + return count; +} diff --git a/common/payload.h b/common/payload.h new file mode 100644 index 0000000..4c95da5 --- /dev/null +++ b/common/payload.h @@ -0,0 +1,71 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PAYLOAD_H +#define _PAYLOAD_H + +#include "abstractprotocol.h" + +#include "payload.pb.h" + +class PayloadProtocol : public AbstractProtocol +{ +public: + enum payloadfield + { + payload_dataPattern, + + // Meta fields + payload_dataPatternMode, + + payload_fieldCount + }; + + PayloadProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~PayloadProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual int protocolFrameSize(int streamIndex = 0) const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual bool isProtocolFrameValueVariable() const; + virtual bool isProtocolFrameSizeVariable() const; + virtual int protocolFrameVariableCount() const; + +private: + OstProto::Payload data; +}; + +#endif diff --git a/common/payload.proto b/common/payload.proto new file mode 100644 index 0000000..bafa4c3 --- /dev/null +++ b/common/payload.proto @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +message Payload { + enum DataPatternMode { + e_dp_fixed_word = 0; + e_dp_inc_byte = 1; + e_dp_dec_byte = 2; + e_dp_random = 3; + } + + // Data Pattern + optional DataPatternMode pattern_mode = 1; + optional uint32 pattern = 2; + + //optional uint32 data_start_ofs = 13; +} + +extend Protocol { + optional Payload payload = 101; +} diff --git a/common/payload.ui b/common/payload.ui new file mode 100644 index 0000000..a7ff9a2 --- /dev/null +++ b/common/payload.ui @@ -0,0 +1,106 @@ + + payload + + + + 0 + 0 + 299 + 114 + + + + Form + + + + + + Type + + + cmbPatternMode + + + + + + + + Fixed Word + + + + + Increment Byte + + + + + Decrement Byte + + + + + Random + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Pattern + + + lePattern + + + + + + + >HH HH HH HH; + + + + + + 11 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/common/payloadconfig.cpp b/common/payloadconfig.cpp new file mode 100644 index 0000000..d06d672 --- /dev/null +++ b/common/payloadconfig.cpp @@ -0,0 +1,84 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "payloadconfig.h" + +#include "payload.h" + +PayloadConfigForm::PayloadConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); +} + +PayloadConfigForm::~PayloadConfigForm() +{ +} + +AbstractProtocolConfigForm* PayloadConfigForm::createInstance() +{ + return new PayloadConfigForm; +} + +void PayloadConfigForm::loadWidget(AbstractProtocol *proto) +{ + cmbPatternMode->setCurrentIndex( + proto->fieldData( + PayloadProtocol::payload_dataPatternMode, + AbstractProtocol::FieldValue + ).toUInt()); + lePattern->setText(uintToHexStr( + proto->fieldData( + PayloadProtocol::payload_dataPattern, + AbstractProtocol::FieldValue + ).toUInt(), 4)); +} + +void PayloadConfigForm::storeWidget(AbstractProtocol *proto) +{ + bool isOk; + + proto->setFieldData( + PayloadProtocol::payload_dataPatternMode, + cmbPatternMode->currentIndex()); + + proto->setFieldData( + PayloadProtocol::payload_dataPattern, + lePattern->text().remove(QChar(' ')).toUInt(&isOk, BASE_HEX)); +} + +void PayloadConfigForm::on_cmbPatternMode_currentIndexChanged(int index) +{ + switch(index) + { + case OstProto::Payload::e_dp_fixed_word: + lePattern->setEnabled(true); + break; + case OstProto::Payload::e_dp_inc_byte: + case OstProto::Payload::e_dp_dec_byte: + case OstProto::Payload::e_dp_random: + lePattern->setDisabled(true); + break; + default: + qWarning("Unhandled/Unknown PatternMode = %d",index); + } +} + + + diff --git a/common/payloadconfig.h b/common/payloadconfig.h new file mode 100644 index 0000000..8ebfeb0 --- /dev/null +++ b/common/payloadconfig.h @@ -0,0 +1,44 @@ +/* +Copyright (C) 2010-2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PAYLOAD_CONFIG_H +#define _PAYLOAD_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_payload.h" + +class PayloadConfigForm : + public AbstractProtocolConfigForm, + private Ui::payload +{ + Q_OBJECT +public: + PayloadConfigForm(QWidget *parent = 0); + virtual ~PayloadConfigForm(); + + static AbstractProtocolConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private slots: + void on_cmbPatternMode_currentIndexChanged(int index); +}; + +#endif diff --git a/common/pcapfileformat.cpp b/common/pcapfileformat.cpp new file mode 100644 index 0000000..e96b684 --- /dev/null +++ b/common/pcapfileformat.cpp @@ -0,0 +1,661 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pcapfileformat.h" + +#include "pdmlreader.h" +#include "ostprotolib.h" +#include "streambase.h" +#include "hexdump.pb.h" + +#include +#include +#include +#include +#include +#include + +static inline quint32 swap32(quint32 val) +{ + return (((val >> 24) && 0x000000FF) | + ((val >> 16) && 0x0000FF00) | + ((val << 16) && 0x00FF0000) | + ((val << 24) && 0xFF000000)); +} + +static inline quint16 swap16(quint16 val) +{ + return (((val >> 8) && 0x00FF) | + ((val << 8) && 0xFF00)); +} + +const quint32 kPcapFileMagic = 0xa1b2c3d4; +const quint32 kPcapFileMagicSwapped = 0xd4c3b2a1; +const quint16 kPcapFileVersionMajor = 2; +const quint16 kPcapFileVersionMinor = 4; +const quint32 kMaxSnapLen = 65535; +const quint32 kDltEthernet = 1; + +PcapFileFormat pcapFileFormat; + +PcapImportOptionsDialog::PcapImportOptionsDialog(QVariantMap *options) + : QDialog(NULL) +{ + setupUi(this); + options_ = options; + + viaPdml->setChecked(options_->value("ViaPdml").toBool()); + doDiff->setChecked(options_->value("DoDiff").toBool()); + + connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); +} + +PcapImportOptionsDialog::~PcapImportOptionsDialog() +{ +} + +void PcapImportOptionsDialog::accept() +{ + options_->insert("ViaPdml", viaPdml->isChecked()); + options_->insert("DoDiff", doDiff->isChecked()); + + QDialog::accept(); +} + +PcapFileFormat::PcapFileFormat() +{ + importOptions_.insert("ViaPdml", true); + importOptions_.insert("DoDiff", true); + + importDialog_ = NULL; +} + +PcapFileFormat::~PcapFileFormat() +{ + delete importDialog_; +} + +bool PcapFileFormat::openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error) +{ + bool isOk = false; + QFile file(fileName); + QTemporaryFile file2; + quint32 magic; + uchar gzipMagic[2]; + int len; + PcapFileHeader fileHdr; + PcapPacketHeader pktHdr; + OstProto::Stream *prevStream = NULL; + uint lastUsec = 0; + int pktCount; + qint64 byteCount = 0; + qint64 byteTotal; + QByteArray pktBuf; + + if (!file.open(QIODevice::ReadOnly)) + goto _err_open; + + len = file.peek((char*)gzipMagic, sizeof(gzipMagic)); + if (len < int(sizeof(gzipMagic))) + goto _err_reading_magic; + + if ((gzipMagic[0] == 0x1f) && (gzipMagic[1] == 0x8b)) + { + QProcess gzip; + + emit status("Decompressing..."); + emit target(0); + + if (!file2.open()) + { + error.append("Unable to open temporary file to uncompress .gz\n"); + goto _err_unzip_fail; + } + + qDebug("decompressing to %s", file2.fileName().toAscii().constData()); + + gzip.setStandardOutputFile(file2.fileName()); + gzip.start(OstProtoLib::gzipPath(), + QStringList() + << "-d" + << "-c" + << fileName); + if (!gzip.waitForStarted(-1)) + { + error.append(QString("Unable to start gzip. Check path in Preferences.\n")); + goto _err_unzip_fail; + } + + if (!gzip.waitForFinished(-1)) + { + error.append(QString("Error running gzip\n")); + goto _err_unzip_fail; + } + + file2.seek(0); + + fd_.setDevice(&file2); + } + else + { + fd_.setDevice(&file); + } + + byteTotal = fd_.device()->size() - sizeof(fileHdr); + + emit status("Reading File Header..."); + emit target(0); + + fd_ >> magic; + + qDebug("magic = %08x", magic); + + if (magic == kPcapFileMagicSwapped) + { + // Toggle Byte order + if (fd_.byteOrder() == QDataStream::BigEndian) + fd_.setByteOrder(QDataStream::LittleEndian); + else + fd_.setByteOrder(QDataStream::BigEndian); + } + else if (magic != kPcapFileMagic) + goto _err_bad_magic; + + fd_ >> fileHdr.versionMajor; + fd_ >> fileHdr.versionMinor; + fd_ >> fileHdr.thisZone; + fd_ >> fileHdr.sigfigs; + fd_ >> fileHdr.snapLen; + fd_ >> fileHdr.network; + + if ((fileHdr.versionMajor != kPcapFileVersionMajor) || + (fileHdr.versionMinor != kPcapFileVersionMinor)) + goto _err_unsupported_version; + +#if 1 + // XXX: we support only Ethernet, for now + if (fileHdr.network != kDltEthernet) + goto _err_unsupported_encap; +#endif + + pktBuf.resize(fileHdr.snapLen); + + if (importOptions_.value("ViaPdml").toBool()) + { + QProcess tshark; + QTemporaryFile pdmlFile; + PdmlReader reader(&streams); + + if (!pdmlFile.open()) + { + error.append("Unable to open temporary file to create PDML\n"); + goto _non_pdml; + } + + qDebug("generating PDML %s", pdmlFile.fileName().toAscii().constData()); + emit status("Generating PDML..."); + emit target(0); + + tshark.setStandardOutputFile(pdmlFile.fileName()); + tshark.start(OstProtoLib::tsharkPath(), + QStringList() + << QString("-r%1").arg(fileName) + << "-otcp.desegment_tcp_streams:FALSE" + << "-Tpdml"); + if (!tshark.waitForStarted(-1)) + { + error.append(QString("Unable to start tshark. Check path in preferences.\n")); + goto _non_pdml; + } + + if (!tshark.waitForFinished(-1)) + { + error.append(QString("Error running tshark\n")); + goto _non_pdml; + } + + connect(&reader, SIGNAL(progress(int)), this, SIGNAL(progress(int))); + + emit status("Reading PDML packets..."); + emit target(100); // in percentage + isOk = reader.read(&pdmlFile, this, &stop_); + + if (stop_) + goto _user_cancel; + + if (!isOk) + { + error.append(QString("Error processing PDML (%1, %2): %3\n") + .arg(reader.lineNumber()) + .arg(reader.columnNumber()) + .arg(reader.errorString())); + goto _exit; + } + + if (!importOptions_.value("DoDiff").toBool()) + goto _exit; + + + // !-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-! + // Let's do the diff ... + // !-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-!-! + + QProcess awk; + QProcess diff; + QTemporaryFile originalTextFile; + QTemporaryFile importedPcapFile; + QTemporaryFile importedTextFile; + QTemporaryFile diffFile; + const QString kAwkFilter = + "/^[^0]/ { " + "printf \" %s \", $1;" + "for (i=4; i %s", + originalTextFile.fileName().toAscii().constData(), + importedTextFile.fileName().toAscii().constData(), + diffFile.fileName().toAscii().constData()); + + emit status("Taking diff..."); + emit target(0); + + diff.setStandardOutputFile(diffFile.fileName()); + diff.start(OstProtoLib::diffPath(), + QStringList() + << "-u" + << "-F^ [1-9]" + << QString("--label=%1 (actual)") + .arg(QFileInfo(fileName).fileName()) + << QString("--label=%1 (imported)") + .arg(QFileInfo(fileName).fileName()) + << originalTextFile.fileName() + << importedTextFile.fileName()); + if (!diff.waitForStarted(-1)) + { + error.append(QString("Unable to start diff. Check path in Preferences.\n") + .arg(diff.exitCode())); + goto _diff_fail; + } + + if (!diff.waitForFinished(-1)) + { + error.append(QString("Error running diff\n")); + goto _diff_fail; + } + + diffFile.close(); + if (diffFile.size()) + { + error.append("There is a diff between the original and imported streams. See details for diff.\n\n\n\n"); + diffFile.open(); + diffFile.seek(0); + error.append(QString(diffFile.readAll())); + } + + goto _exit; + } + +_non_pdml: + emit status("Reading Packets..."); + emit target(100); // in percentage + pktCount = 1; + while (!fd_.atEnd()) + { + OstProto::Stream *stream = streams.add_stream(); + OstProto::Protocol *proto = stream->add_protocol(); + OstProto::HexDump *hexDump = proto->MutableExtension(OstProto::hexDump); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kHexDumpFieldNumber); + + readPacket(pktHdr, pktBuf); + + // validations on inclLen <= origLen && inclLen <= snapLen + Q_ASSERT(pktHdr.inclLen <= fileHdr.snapLen); // TODO: convert to if + + hexDump->set_content(pktBuf.data(), pktHdr.inclLen); + hexDump->set_pad_until_end(false); + + stream->mutable_stream_id()->set_id(pktCount); + stream->mutable_core()->set_is_enabled(true); + stream->mutable_core()->set_frame_len(pktHdr.inclLen+4); // FCS + + // setup packet rate to the timing in pcap (as close as possible) + const uint kUsecsInSec = uint(1e6); + uint usec = (pktHdr.tsSec*kUsecsInSec + pktHdr.tsUsec); + uint delta = usec - lastUsec; + + if ((pktCount != 1) && delta) + stream->mutable_control()->set_packets_per_sec(kUsecsInSec/delta); + + if (prevStream) + prevStream->mutable_control()->CopyFrom(stream->control()); + + lastUsec = usec; + prevStream = stream; + pktCount++; + qDebug("pktCount = %d", pktCount); + byteCount += pktHdr.inclLen + sizeof(pktHdr); + emit progress(int(byteCount*100/byteTotal)); // in percentage + if (stop_) + goto _user_cancel; + } + + isOk = true; + goto _exit; + +_user_cancel: + isOk = true; + goto _exit; + +_diff_fail: + goto _exit; + +_err_unsupported_encap: + error = QString(tr("%1 has non-ethernet encapsulation (%2) which is " + "not supported - Sorry!")) + .arg(QFileInfo(fileName).fileName()).arg(fileHdr.network); + goto _exit; + +_err_unsupported_version: + error = QString(tr("%1 is in PCAP version %2.%3 format which is " + "not supported - Sorry!")) + .arg(fileName).arg(fileHdr.versionMajor).arg(fileHdr.versionMinor); + goto _exit; + +_err_bad_magic: + error = QString(tr("%1 is not a valid PCAP file")).arg(fileName); + goto _exit; + +#if 0 +_err_truncated: + error = QString(tr("%1 is too short")).arg(fileName); + goto _exit; +#endif + +_err_unzip_fail: + goto _exit; + +_err_reading_magic: + error = QString(tr("Unable to read magic from %1")).arg(fileName); + goto _exit; + +_err_open: + error = QString(tr("Unable to open file: %1")).arg(fileName); + goto _exit; + +_exit: + file.close(); + return isOk; +} + +/*! + Reads packet meta data into pktHdr and packet content into buf. + + Returns true if packet is read successfully, false otherwise. +*/ +bool PcapFileFormat::readPacket(PcapPacketHeader &pktHdr, QByteArray &pktBuf) +{ + quint32 len; + + // TODO: chk fd_.status() + + // read PcapPacketHeader + fd_ >> pktHdr.tsSec; + fd_ >> pktHdr.tsUsec; + fd_ >> pktHdr.inclLen; + fd_ >> pktHdr.origLen; + + // TODO: chk fd_.status() + + // XXX: should never be required, but we play safe + if (quint32(pktBuf.size()) < pktHdr.inclLen) + pktBuf.resize(pktHdr.inclLen); + + // read Pkt contents + len = fd_.readRawData(pktBuf.data(), pktHdr.inclLen); // TODO: use while? + + Q_ASSERT(len == pktHdr.inclLen); // TODO: remove assert + pktBuf.resize(len); + + return true; +} + +bool PcapFileFormat::saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error) +{ + bool isOk = false; + QFile file(fileName); + PcapFileHeader fileHdr; + PcapPacketHeader pktHdr; + QByteArray pktBuf; + + if (!file.open(QIODevice::WriteOnly)) + goto _err_open; + + fd_.setDevice(&file); + + fileHdr.magicNumber = kPcapFileMagic; + fileHdr.versionMajor = kPcapFileVersionMajor; + fileHdr.versionMinor = kPcapFileVersionMinor; + fileHdr.thisZone = 0; + fileHdr.sigfigs = 0; + fileHdr.snapLen = kMaxSnapLen; + fileHdr.network = kDltEthernet; + + fd_ << fileHdr.magicNumber; + fd_ << fileHdr.versionMajor; + fd_ << fileHdr.versionMinor; + fd_ << fileHdr.thisZone; + fd_ << fileHdr.sigfigs; + fd_ << fileHdr.snapLen; + fd_ << fileHdr.network; + + pktBuf.resize(kMaxSnapLen); + + emit status("Writing Packets..."); + emit target(streams.stream_size()); + + pktHdr.tsSec = 0; + pktHdr.tsUsec = 0; + for (int i = 0; i < streams.stream_size(); i++) + { + StreamBase s; + + s.setId(i); + s.protoDataCopyFrom(streams.stream(i)); + // TODO: expand frameIndex for each stream + s.frameValue((uchar*)pktBuf.data(), pktBuf.size(), 0); + + pktHdr.inclLen = s.frameProtocolLength(0); // FIXME: stream index = 0 + pktHdr.origLen = s.frameLen() - 4; // FCS; FIXME: Hardcoding + + qDebug("savepcap i=%d, incl/orig len = %d/%d", i, + pktHdr.inclLen, pktHdr.origLen); + + if (pktHdr.inclLen > fileHdr.snapLen) + pktHdr.inclLen = fileHdr.snapLen; + + fd_ << pktHdr.tsSec; + fd_ << pktHdr.tsUsec; + fd_ << pktHdr.inclLen; + fd_ << pktHdr.origLen; + fd_.writeRawData(pktBuf.data(), pktHdr.inclLen); + + if (s.packetRate()) + pktHdr.tsUsec += quint32(1e6/s.packetRate()); + if (pktHdr.tsUsec >= 1000000) + { + pktHdr.tsSec++; + pktHdr.tsUsec -= 1000000; + } + + emit progress(i); + } + + file.close(); + + isOk = true; + goto _exit; + +_err_open: + error = QString(tr("Unable to open file: %1")).arg(fileName); + goto _exit; + +_exit: + return isOk; +} + +QDialog* PcapFileFormat::openOptionsDialog() +{ + if (!importDialog_) + importDialog_ = new PcapImportOptionsDialog(&importOptions_); + + return importDialog_; +} + +bool PcapFileFormat::isMyFileFormat(const QString /*fileName*/) +{ + // TODO + return true; +} + +bool PcapFileFormat::isMyFileType(const QString fileType) +{ + if (fileType.startsWith("PCAP")) + return true; + else + return false; +} diff --git a/common/pcapfileformat.h b/common/pcapfileformat.h new file mode 100644 index 0000000..064aaf1 --- /dev/null +++ b/common/pcapfileformat.h @@ -0,0 +1,87 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _PCAP_FILE_FORMAT_H +#define _PCAP_FILE_FORMAT_H + +#include "abstractfileformat.h" +#include "ui_pcapfileimport.h" + +#include +#include + +class PcapImportOptionsDialog: public QDialog, public Ui::PcapFileImport +{ +public: + PcapImportOptionsDialog(QVariantMap *options); + ~PcapImportOptionsDialog(); + +private slots: + void accept(); + +private: + QVariantMap *options_; +}; + +class PdmlReader; +class PcapFileFormat : public AbstractFileFormat +{ + friend class PdmlReader; + +public: + PcapFileFormat(); + ~PcapFileFormat(); + + bool openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error); + bool saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error); + + virtual QDialog* openOptionsDialog(); + + bool isMyFileFormat(const QString fileName); + bool isMyFileType(const QString fileType); + +private: + typedef struct { + quint32 magicNumber; /* magic number */ + quint16 versionMajor; /* major version number */ + quint16 versionMinor; /* minor version number */ + qint32 thisZone; /* GMT to local correction */ + quint32 sigfigs; /* accuracy of timestamps */ + quint32 snapLen; /* max length of captured packets, in octets */ + quint32 network; /* data link type */ + } PcapFileHeader; + + typedef struct { + quint32 tsSec; /* timestamp seconds */ + quint32 tsUsec; /* timestamp microseconds */ + quint32 inclLen; /* number of octets of packet saved in file */ + quint32 origLen; /* actual length of packet */ + } PcapPacketHeader; + + bool readPacket(PcapPacketHeader &pktHdr, QByteArray &pktBuf); + + QDataStream fd_; + QVariantMap importOptions_; + PcapImportOptionsDialog *importDialog_; +}; + +extern PcapFileFormat pcapFileFormat; + +#endif diff --git a/common/pcapfileimport.ui b/common/pcapfileimport.ui new file mode 100644 index 0000000..8718c45 --- /dev/null +++ b/common/pcapfileimport.ui @@ -0,0 +1,132 @@ + + PcapFileImport + + + + 0 + 0 + 326 + 93 + + + + PCAP import options + + + + + + Intelligent Import (via PDML) + + + + + + + + + + 0 + 0 + + + + + 16 + 16 + + + + + + + + false + + + Do a diff after import + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + PcapFileImport + accept() + + + 249 + 81 + + + 157 + 90 + + + + + buttonBox + rejected() + PcapFileImport + reject() + + + 249 + 81 + + + 258 + 90 + + + + + viaPdml + toggled(bool) + doDiff + setEnabled(bool) + + + 15 + 16 + + + 37 + 42 + + + + + viaPdml + toggled(bool) + doDiff + setChecked(bool) + + + 151 + 14 + + + 150 + 34 + + + + + diff --git a/common/pdmlfileformat.cpp b/common/pdmlfileformat.cpp new file mode 100644 index 0000000..e567d99 --- /dev/null +++ b/common/pdmlfileformat.cpp @@ -0,0 +1,170 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pdmlfileformat.h" + +#include "ostprotolib.h" +#include "pdmlreader.h" + +#include +#include + +PdmlFileFormat pdmlFileFormat; + +PdmlFileFormat::PdmlFileFormat() +{ +} + +PdmlFileFormat::~PdmlFileFormat() +{ +} + +bool PdmlFileFormat::openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error) +{ + bool isOk = false; + QFile file(fileName); + PdmlReader *reader = new PdmlReader(&streams); + + if (!file.open(QIODevice::ReadOnly)) + goto _open_fail; + + connect(reader, SIGNAL(progress(int)), this, SIGNAL(progress(int))); + emit status("Reading PDML packets..."); + emit target(100); // in percentage + + isOk = reader->read(&file, NULL, &stop_); + + if (stop_) + goto _user_cancel; + + if (!isOk) + { + error.append(QString("Error processing PDML (%1, %2): %3\n") + .arg(reader->lineNumber()) + .arg(reader->columnNumber()) + .arg(reader->errorString())); + goto _exit; + } + + goto _exit; + +_open_fail: + isOk = false; + +_user_cancel: +_exit: + delete reader; + + return isOk; +} + +bool PdmlFileFormat::saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error) +{ + bool isOk = false; + QTemporaryFile pcapFile; + AbstractFileFormat *fmt = AbstractFileFormat::fileFormatFromType("PCAP"); + QProcess tshark; + + Q_ASSERT(fmt); + + if (!pcapFile.open()) + { + error.append("Unable to open temporary file to create PCAP\n"); + goto _fail; + } + + qDebug("intermediate PCAP %s", pcapFile.fileName().toAscii().constData()); + + connect(fmt, SIGNAL(target(int)), this, SIGNAL(target(int))); + connect(fmt, SIGNAL(progress(int)), this, SIGNAL(progress(int))); + + emit status("Writing intermediate PCAP file..."); + isOk = fmt->saveStreams(streams, pcapFile.fileName(), error); + + qDebug("generating PDML %s", fileName.toAscii().constData()); + emit status("Converting PCAP to PDML..."); + emit target(0); + + tshark.setStandardOutputFile(fileName); + tshark.start(OstProtoLib::tsharkPath(), + QStringList() + << QString("-r%1").arg(pcapFile.fileName()) + << "-Tpdml"); + if (!tshark.waitForStarted(-1)) + { + error.append(QString("Unable to start tshark. Check path in preferences.\n")); + goto _fail; + } + + if (!tshark.waitForFinished(-1)) + { + error.append(QString("Error running tshark\n")); + goto _fail; + } + + isOk = true; +_fail: + return isOk; +} + +bool PdmlFileFormat::isMyFileFormat(const QString fileName) +{ + bool ret = false; + QFile file(fileName); + QByteArray buf; + QXmlStreamReader xml; + + if (!file.open(QIODevice::ReadOnly)) + goto _exit; + + xml.setDevice(&file); + + xml.readNext(); + if (xml.hasError() || !xml.isStartDocument()) + goto _close_exit; + + // skip everything until the start of the first element + while (!xml.isStartElement()) + { + xml.readNext(); + if (xml.hasError()) + goto _close_exit; + } + + if (!xml.hasError() && xml.isStartElement() && (xml.name() == "pdml")) + ret = true; + else + ret = false; + +_close_exit: + xml.clear(); + file.close(); +_exit: + return ret; +} + +bool PdmlFileFormat::isMyFileType(const QString fileType) +{ + if (fileType.startsWith("PDML")) + return true; + else + return false; +} diff --git a/common/pdmlfileformat.h b/common/pdmlfileformat.h new file mode 100644 index 0000000..e05026a --- /dev/null +++ b/common/pdmlfileformat.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ +#ifndef _PDML_FILE_FORMAT_H +#define _PDML_FILE_FORMAT_H + +#include "abstractfileformat.h" + +class PdmlFileFormat : public AbstractFileFormat +{ +public: + PdmlFileFormat(); + ~PdmlFileFormat(); + + virtual bool openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error); + virtual bool saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error); + + bool isMyFileFormat(const QString fileName); + bool isMyFileType(const QString fileType); + +}; + +extern PdmlFileFormat pdmlFileFormat; + +#endif diff --git a/common/pdmlprotocol.cpp b/common/pdmlprotocol.cpp new file mode 100644 index 0000000..a682aeb --- /dev/null +++ b/common/pdmlprotocol.cpp @@ -0,0 +1,248 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pdmlprotocol.h" + +/*! + \class PdmlProtocol + + PdmlProtocol is the base class which provides the interface for all + PDML decode helper protocols + + All Pdml helper classes derived from PdmlProtocol MUST register + themselves with PdmlReader. When PdmlReader encounters a 'proto' tag + in the PDML during parsing, it instantiates the corresponding helper + PdmlProtocol class and calls its methods to decode the protocol. + + A subclass MUST initialize the following inherited protected variables + in its constructor - + - ostProtoId_ + - fieldMap_ + + A subclass typically needs to reimplement the following methods - + - createInstance() + + Depending on certain conditions, subclasses may need to reimplement + the following additional methods - + - unknownFieldHandler() + - preProtocolHandler() + - postProtocolHandler() + + See the description of the methods for more information. + + Use the SamplePdmlProtocol implementation as boilerplate code and + for guidelines and tips +*/ + +/*! + Constructs the PdmlProtocol +*/ +PdmlProtocol::PdmlProtocol() +{ + ostProtoId_ = -1; +} + +/*! + Destroys the PdmlProtocol +*/ +PdmlProtocol::~PdmlProtocol() +{ +} + +/*! + Allocates and returns a new instance of the class + + Caller is responsible for freeing up after use. Subclasses MUST implement + this function and register it with PdmlReader +*/ +PdmlProtocol* PdmlProtocol::createInstance() +{ + return new PdmlProtocol(); +} + +/*! + Returns the protocol's field number as defined in message 'Protocol', enum 'k' + (file: protocol.proto) +*/ +int PdmlProtocol::ostProtoId() const +{ + return ostProtoId_; +} + +/*! + Returns true if name is a 'known' field that can be directly mapped + to the protobuf field +*/ +bool PdmlProtocol::hasField(QString name) const +{ + return fieldMap_.contains(name); +} + +/*! + Returns the protocol's protobuf field number corresponding to name +*/ +int PdmlProtocol::fieldId(QString name) const +{ + return fieldMap_.value(name); +} + +/*! + This method is called by PdmlReader before any fields within the protocol + are processed. All attributes associated with the 'proto' tag in the PDML + are passed to this method + + Use this method to do any special handling that may be required for + preprocessing +*/ +void PdmlProtocol::preProtocolHandler(QString /*name*/, + const QXmlStreamAttributes& /*attributes*/, + int /*expectedPos*/, OstProto::Protocol* /*pbProto*/, + OstProto::Stream* /*stream*/) +{ + return; // do nothing! +} + +/*! + This method is called by PdmlReader when it encounters a nested + protocol in the PDML i.e. a protocol within a protocol or a protocol + within a field + + This is a notification to the protocol that protocol processing will + be ending prematurely. postProtocolHandler() will still be called in + such cases. +*/ +void PdmlProtocol::prematureEndHandler(int /*pos*/, + OstProto::Protocol* /*pbProto*/, OstProto::Stream* /*stream*/) +{ + return; // do nothing! +} + +/*! + This method is called by PdmlReader after all fields within the protocol + are processed. + + Use this method to do any special handling that may be required for + postprocessing +*/ +void PdmlProtocol::postProtocolHandler(OstProto::Protocol* /*pbProto*/, + OstProto::Stream* /*stream*/) +{ + return; // do nothing! +} + + +/*! + This method is called by PdmlReader for each field in the protocol + + Depending on whether it is a known or unknown field, the virtual methods + knownFieldHandler() and unknownFieldHandler() are invoked +*/ +void PdmlProtocol::fieldHandler(QString name, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream) +{ + if (hasField(name)) + { + QString valueHexStr = attributes.value("value").toString(); + + qDebug("\t(KNOWN) fieldName:%s, value:%s", + name.toAscii().constData(), + valueHexStr.toAscii().constData()); + + knownFieldHandler(name, valueHexStr, pbProto); + } + else + { + int pos = -1; + int size = -1; + + if (!attributes.value("pos").isEmpty()) + pos = attributes.value("pos").toString().toInt(); + if (!attributes.value("size").isEmpty()) + size = attributes.value("size").toString().toInt(); + + qDebug("\t(UNKNOWN) fieldName:%s, pos:%d, size:%d", + name.toAscii().constData(), pos, size); + + unknownFieldHandler(name, pos, size, attributes, pbProto, stream); + } +} + +/*! + Handles a 'known' field + + Uses protobuf reflection interface to set the protobuf field name to + valueHexStr as per the field's datatype +*/ +void PdmlProtocol::knownFieldHandler(QString name, QString valueHexStr, + OstProto::Protocol *pbProto) +{ + const google::protobuf::Reflection *protoRefl = pbProto->GetReflection(); + const google::protobuf::FieldDescriptor *extDesc = + protoRefl->FindKnownExtensionByNumber(ostProtoId()); + + google::protobuf::Message *msg = + protoRefl->MutableMessage(pbProto,extDesc); + + const google::protobuf::Reflection *msgRefl = msg->GetReflection(); + const google::protobuf::FieldDescriptor *fieldDesc = + msg->GetDescriptor()->FindFieldByNumber(fieldId(name)); + + bool isOk; + + Q_ASSERT(fieldDesc != NULL); + switch(fieldDesc->cpp_type()) + { + case google::protobuf::FieldDescriptor::CPPTYPE_BOOL: + msgRefl->SetBool(msg, fieldDesc, bool(valueHexStr.toUInt(&isOk))); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_ENUM: // TODO + case google::protobuf::FieldDescriptor::CPPTYPE_UINT32: + msgRefl->SetUInt32(msg, fieldDesc, + valueHexStr.toUInt(&isOk, kBaseHex)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_UINT64: + msgRefl->SetUInt64(msg, fieldDesc, + valueHexStr.toULongLong(&isOk, kBaseHex)); + break; + case google::protobuf::FieldDescriptor::CPPTYPE_STRING: + { + QByteArray hexVal = QByteArray::fromHex(valueHexStr.toUtf8()); + std::string str(hexVal.constData(), hexVal.size()); + msgRefl->SetString(msg, fieldDesc, str); + break; + } + default: + qDebug("%s: unhandled cpptype = %d", __FUNCTION__, + fieldDesc->cpp_type()); + } +} + +/*! + Handles a 'unknown' field + + The default implementation does nothing. Subclasses may need to implement + this if the protocol contains 'unknown' fields. +*/ +void PdmlProtocol::unknownFieldHandler(QString /*name*/, + int /*pos*/, int /*size*/, const QXmlStreamAttributes& /*attributes*/, + OstProto::Protocol* /*pbProto*/, OstProto::Stream* /*stream*/) +{ + return; // do nothing! +} diff --git a/common/pdmlprotocol.h b/common/pdmlprotocol.h new file mode 100644 index 0000000..011fcb6 --- /dev/null +++ b/common/pdmlprotocol.h @@ -0,0 +1,68 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PDML_PROTOCOL_H +#define _PDML_PROTOCOL_H + +#include "protocol.pb.h" + +#include +#include +#include +#include + +const int kBaseHex = 16; + +class PdmlProtocol +{ +public: + virtual ~PdmlProtocol(); + + static PdmlProtocol* createInstance(); + + int ostProtoId() const; + bool hasField(QString name) const; + int fieldId(QString name) const; + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void prematureEndHandler(int pos, OstProto::Protocol *pbProto, + OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); + + void fieldHandler(QString name, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + void knownFieldHandler(QString name, QString valueHexStr, + OstProto::Protocol *pbProto); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + +protected: + PdmlProtocol(); + + //!< Protocol's field number as defined in message 'Protocol', enum 'k' + int ostProtoId_; + //!< Map of PDML field names to protobuf field numbers for 'known' fields + QMap fieldMap_; +}; + +#endif diff --git a/common/pdmlprotocols.cpp b/common/pdmlprotocols.cpp new file mode 100644 index 0000000..c64d46d --- /dev/null +++ b/common/pdmlprotocols.cpp @@ -0,0 +1,195 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pdmlprotocols.h" + +#include "hexdump.pb.h" + +// ---------------------------------------------------------- // +// PdmlUnknownProtocol // +// ---------------------------------------------------------- // + +PdmlUnknownProtocol::PdmlUnknownProtocol() +{ + ostProtoId_ = OstProto::Protocol::kHexDumpFieldNumber; + + endPos_ = expPos_ = -1; +} + +PdmlProtocol* PdmlUnknownProtocol::createInstance() +{ + return new PdmlUnknownProtocol(); +} + +void PdmlUnknownProtocol::preProtocolHandler(QString /*name*/, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol* /*pbProto*/, OstProto::Stream *stream) +{ + bool isOk; + int size; + int pos = attributes.value("pos").toString().toUInt(&isOk); + if (!isOk) + { + if (expectedPos >= 0) + expPos_ = pos = expectedPos; + else + goto _skip_pos_size_proc; + } + + size = attributes.value("size").toString().toUInt(&isOk); + if (!isOk) + goto _skip_pos_size_proc; + + // If pos+size goes beyond the frame length, this is a "reassembled" + // protocol and should be skipped + if ((pos + size) > int(stream->core().frame_len())) + goto _skip_pos_size_proc; + + expPos_ = pos; + endPos_ = expPos_ + size; + +_skip_pos_size_proc: + OstProto::HexDump *hexDump = stream->mutable_protocol( + stream->protocol_size()-1)->MutableExtension(OstProto::hexDump); + hexDump->set_pad_until_end(false); +} + +void PdmlUnknownProtocol::prematureEndHandler(int pos, + OstProto::Protocol* /*pbProto*/, OstProto::Stream* /*stream*/) +{ + endPos_ = pos; +} + +void PdmlUnknownProtocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream) +{ + OstProto::HexDump *hexDump = pbProto->MutableExtension(OstProto::hexDump); + + // Skipped field(s) at end? Pad with zero! + if (endPos_ > expPos_) + { + QByteArray hexVal(endPos_ - expPos_, char(0)); + + hexDump->mutable_content()->append(hexVal.constData(), hexVal.size()); + expPos_ += hexVal.size(); + } + + qDebug(" hexdump: expPos_ = %d, endPos_ = %d\n", expPos_, endPos_); + + // If empty for some reason, remove the protocol + if (hexDump->content().size() == 0) + stream->mutable_protocol()->RemoveLast(); + + endPos_ = expPos_ = -1; +} + +void PdmlUnknownProtocol::unknownFieldHandler(QString name, int pos, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + OstProto::HexDump *hexDump = pbProto->MutableExtension(OstProto::hexDump); + + qDebug(" hexdump: %s, pos = %d, expPos_ = %d, endPos_ = %d\n", + name.toAscii().constData(), + pos, expPos_, endPos_); + + // Skipped field? Pad with zero! + if ((pos > expPos_) && (expPos_ < endPos_)) + { + QByteArray hexVal(pos - expPos_, char(0)); + + hexDump->mutable_content()->append(hexVal.constData(), hexVal.size()); + expPos_ += hexVal.size(); + } + + if (pos == expPos_) + { + QByteArray hexVal = attributes.value("unmaskedvalue").isEmpty() ? + QByteArray::fromHex(attributes.value("value").toString().toUtf8()) : + QByteArray::fromHex(attributes.value("unmaskedvalue").toString().toUtf8()); + + hexDump->mutable_content()->append(hexVal.constData(), hexVal.size()); + expPos_ += hexVal.size(); + } +} + + +// ---------------------------------------------------------- // +// PdmlGenInfoProtocol // +// ---------------------------------------------------------- // + +PdmlGenInfoProtocol::PdmlGenInfoProtocol() +{ +} + +PdmlProtocol* PdmlGenInfoProtocol::createInstance() +{ + return new PdmlGenInfoProtocol(); +} + +// ---------------------------------------------------------- // +// PdmlFrameProtocol // +// ---------------------------------------------------------- // + +PdmlFrameProtocol::PdmlFrameProtocol() +{ +} + +PdmlProtocol* PdmlFrameProtocol::createInstance() +{ + return new PdmlFrameProtocol(); +} + +void PdmlFrameProtocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol* /*pbProto*/, OstProto::Stream *stream) +{ + if (name == "frame.len") + { + int len = -1; + + if (!attributes.value("show").isEmpty()) + len = attributes.value("show").toString().toInt(); + stream->mutable_core()->set_frame_len(len+4); // TODO:check FCS + } + else if (name == "frame.time_delta") + { + if (!attributes.value("show").isEmpty()) + { + QString delta = attributes.value("show").toString(); + int decimal = delta.indexOf('.'); + + if (decimal >= 0) + { + const uint kNsecsInSec = 1000000000; + uint sec = delta.left(decimal).toUInt(); + uint nsec = delta.mid(decimal+1).toUInt(); + uint ipg = sec*kNsecsInSec + nsec; + + if (ipg) + { + stream->mutable_control()->set_packets_per_sec( + kNsecsInSec/ipg); + } + + qDebug("sec.nsec = %u.%u, ipg = %u", sec, nsec, ipg); + } + } + } +} diff --git a/common/pdmlprotocols.h b/common/pdmlprotocols.h new file mode 100644 index 0000000..0747df1 --- /dev/null +++ b/common/pdmlprotocols.h @@ -0,0 +1,71 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PDML_PROTOCOLS_H +#define _PDML_PROTOCOLS_H + +#include "pdmlprotocol.h" + +class PdmlUnknownProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void prematureEndHandler(int pos, OstProto::Protocol *pbProto, + OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); +protected: + PdmlUnknownProtocol(); + +private: + int endPos_; + int expPos_; +}; + +class PdmlGenInfoProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + +protected: + PdmlGenInfoProtocol(); + +}; + +class PdmlFrameProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + +protected: + PdmlFrameProtocol(); +}; + +#endif diff --git a/common/pdmlreader.cpp b/common/pdmlreader.cpp new file mode 100644 index 0000000..7b5eb9d --- /dev/null +++ b/common/pdmlreader.cpp @@ -0,0 +1,548 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pdmlreader.h" + +#include "abstractprotocol.h" +#include "hexdump.pb.h" +#include "pcapfileformat.h" +#include "streambase.h" + +#include "pdmlprotocols.h" + +#include "arppdml.h" +#include "eth2pdml.h" +#include "llcpdml.h" +#include "icmppdml.h" +#include "icmp6pdml.h" +#include "igmppdml.h" +#include "ip4pdml.h" +#include "ip6pdml.h" +#include "mldpdml.h" +#include "svlanpdml.h" +#include "tcppdml.h" +#include "textprotopdml.h" +#include "udppdml.h" +#include "vlanpdml.h" + +PdmlReader::PdmlReader(OstProto::StreamConfigList *streams) +{ + //gPdmlReader = this; + pcap_ = NULL; + streams_ = streams; + + currentStream_ = NULL; + prevStream_ = NULL; + + stop_ = NULL; + + factory_.insert("hexdump", PdmlUnknownProtocol::createInstance); + factory_.insert("geninfo", PdmlGenInfoProtocol::createInstance); + factory_.insert("frame", PdmlFrameProtocol::createInstance); + + factory_.insert("arp", PdmlArpProtocol::createInstance); + factory_.insert("eth", PdmlEthProtocol::createInstance); + factory_.insert("http", PdmlTextProtocol::createInstance); + factory_.insert("icmp", PdmlIcmpProtocol::createInstance); + factory_.insert("icmpv6", PdmlIcmp6Protocol::createInstance); + factory_.insert("igmp", PdmlIgmpProtocol::createInstance); + factory_.insert("ieee8021ad", PdmlSvlanProtocol::createInstance); + factory_.insert("imap", PdmlTextProtocol::createInstance); + factory_.insert("ip", PdmlIp4Protocol::createInstance); + factory_.insert("ipv6", PdmlIp6Protocol::createInstance); + factory_.insert("llc", PdmlLlcProtocol::createInstance); + factory_.insert("nntp", PdmlTextProtocol::createInstance); + factory_.insert("pop", PdmlTextProtocol::createInstance); + factory_.insert("rtsp", PdmlTextProtocol::createInstance); + factory_.insert("sdp", PdmlTextProtocol::createInstance); + factory_.insert("sip", PdmlTextProtocol::createInstance); + factory_.insert("smtp", PdmlTextProtocol::createInstance); + factory_.insert("tcp", PdmlTcpProtocol::createInstance); + factory_.insert("udp", PdmlUdpProtocol::createInstance); + factory_.insert("udplite", PdmlUdpProtocol::createInstance); + factory_.insert("vlan", PdmlVlanProtocol::createInstance); +} + +PdmlReader::~PdmlReader() +{ +} + +bool PdmlReader::read(QIODevice *device, PcapFileFormat *pcap, bool *stop) +{ + setDevice(device); + pcap_ = pcap; + stop_ = stop; + + while (!atEnd()) + { + readNext(); + if (isStartElement()) + { + if (name() == "pdml") + readPdml(); + else + raiseError("Not a pdml file!"); + } + } + + if (error() && (errorString() != "USER-CANCEL")) + { + qDebug("Line %lld", lineNumber()); + qDebug("Col %lld", columnNumber()); + qDebug("%s", errorString().toAscii().constData()); + return false; + } + return true; +} + +// TODO: use a temp pool to avoid a lot of new/delete +PdmlProtocol* PdmlReader::allocPdmlProtocol(QString protoName) +{ + // If protoName is not known, we use a hexdump + if (!factory_.contains(protoName)) + protoName = "hexdump"; + + // If MLD is not supported by the creator of the PDML, we interpret + // ICMPv6 as ICMP since our implementation of the ICMPv6 PDML protocol + // exists just to distinguish between MLD and ICMP. Non MLD ICMPv6 is + // also handled by ICMP only + if (!isMldSupport_ && (protoName == "icmpv6")) + protoName = "icmp"; + + return (*(factory_.value(protoName)))(); +} + +void PdmlReader::freePdmlProtocol(PdmlProtocol *proto) +{ + delete proto; +} + +bool PdmlReader::isDontCareProto() +{ + Q_ASSERT(isStartElement() && name() == "proto"); + + QStringRef protoName = attributes().value("name"); + + if (protoName.isEmpty() || (protoName == "expert")) + return true; + + return false; +} + +void PdmlReader::skipElement() +{ + Q_ASSERT(isStartElement()); + + qDebug("skipping element - <%s>", + name().toString().toAscii().constData()); + while (!atEnd()) + { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) + skipElement(); + } +} + +void PdmlReader::readPdml() +{ + QStringList creator; + + Q_ASSERT(isStartElement() && name() == "pdml"); + + isMldSupport_ = true; + creator = attributes().value("creator").toString().split('/'); + if ((creator.size() >= 2) && (creator.at(0) == "wireshark")) + { + QList minMldVer; + minMldVer << 1 << 5 << 0; + QStringList version = creator.at(1).split('.'); + + for (int i = 0; i < qMin(version.size(), minMldVer.size()); i++) + { + if (version.at(i).toUInt() < minMldVer.at(i)) + { + isMldSupport_ = false; + break; + } + } + } + + packetCount_ = 1; + + while (!atEnd()) + { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) + { + if (name() == "packet") + readPacket(); + else + skipElement(); + } + } +} + +void PdmlReader::readPacket() +{ + PcapFileFormat::PcapPacketHeader pktHdr; + + Q_ASSERT(isStartElement() && name() == "packet"); + + qDebug("%s: packetNum = %d", __FUNCTION__, packetCount_); + + skipUntilEnd_ = false; + + // XXX: we play dumb and convert each packet to a stream, for now + prevStream_ = currentStream_; + currentStream_ = streams_->add_stream(); + currentStream_->mutable_stream_id()->set_id(packetCount_); + currentStream_->mutable_core()->set_is_enabled(true); + + // Set to a high number; will get reset to correct value during parse + currentStream_->mutable_core()->set_frame_len(16384); // FIXME: Hard coding! + + expPos_ = 0; + + if (pcap_) + pcap_->readPacket(pktHdr, pktBuf_); + + while (!atEnd()) + { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) + { + if (skipUntilEnd_) + skipElement(); + else if (name() == "proto") + readProto(); + else if (name() == "field") + readField(NULL, NULL); // TODO: top level field!!!! + else + skipElement(); + } + } + + currentStream_->mutable_core()->set_name(""); // FIXME + + // If trailing bytes are missing, add those from the pcap + if ((expPos_ < pktBuf_.size()) && pcap_) + { + OstProto::Protocol *proto = currentStream_->add_protocol(); + OstProto::HexDump *hexDump = proto->MutableExtension( + OstProto::hexDump); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kHexDumpFieldNumber); + + qDebug("adding trailing %d bytes starting from %d", + pktBuf_.size() - expPos_, expPos_); + hexDump->set_content(pktBuf_.constData() + expPos_, + pktBuf_.size() - expPos_); + hexDump->set_pad_until_end(false); + } + + packetCount_++; + emit progress(int(characterOffset()*100/device()->size())); // in % + if (prevStream_) + prevStream_->mutable_control()->CopyFrom(currentStream_->control()); + if (stop_ && *stop_) + raiseError("USER-CANCEL"); +} + +void PdmlReader::readProto() +{ + PdmlProtocol *pdmlProto = NULL; + OstProto::Protocol *pbProto = NULL; + + Q_ASSERT(isStartElement() && name() == "proto"); + + QString protoName; + int pos = -1; + int size = -1; + + if (!attributes().value("name").isEmpty()) + protoName = attributes().value("name").toString(); + if (!attributes().value("pos").isEmpty()) + pos = attributes().value("pos").toString().toInt(); + if (!attributes().value("size").isEmpty()) + size = attributes().value("size").toString().toInt(); + + qDebug("proto: %s, pos = %d, expPos_ = %d, size = %d", + protoName.toAscii().constData(), pos, expPos_, size); + + // This is a heuristic to skip protocols which are not part of + // this frame, but of a reassembled segment spanning several frames + // 1. Proto starting pos is 0, but we've already seen some protocols + // 2. Protocol Size exceeds frame length + if (((pos == 0) && (currentStream_->protocol_size() > 0)) + || ((pos + size) > int(currentStream_->core().frame_len()))) + { + skipElement(); + return; + } + + if (isDontCareProto()) + { + skipElement(); + return; + } + + // if we detect a gap between subsequent protocols, we "fill-in" + // with a "hexdump" from the pcap + if (pos > expPos_ && pcap_) + { + appendHexDumpProto(expPos_, pos - expPos_); + expPos_ = pos; + } + + // for unknown protocol, read a hexdump from the pcap + if (!factory_.contains(protoName) && pcap_) + { + int size = -1; + + if (!attributes().value("size").isEmpty()) + size = attributes().value("size").toString().toInt(); + + // Check if this proto is a subset of previous proto - if so, do nothing + if ((pos >= 0) && (size > 0) && ((pos + size) <= expPos_)) + { + qDebug("subset proto"); + skipElement(); + return; + } + + if (pos >= 0 && size > 0 + && ((pos + size) <= pktBuf_.size())) + { + appendHexDumpProto(pos, size); + expPos_ += size; + + skipElement(); + return; + } + } + + pdmlProto = appendPdmlProto(protoName, &pbProto); + + qDebug("%s: preProtocolHandler(expPos = %d)", + protoName.toAscii().constData(), expPos_); + pdmlProto->preProtocolHandler(protoName, attributes(), expPos_, pbProto, + currentStream_); + + while (!atEnd()) + { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) + { + if (name() == "proto") + { + // an embedded proto + qDebug("embedded proto: %s\n", attributes().value("name") + .toString().toAscii().constData()); + + if (isDontCareProto()) + { + skipElement(); + continue; + } + + // if we are in the midst of processing a protocol, we + // end it prematurely before we start processing the + // embedded protocol + // + // XXX: pdmlProto may be NULL for a sequence of embedded protos + if (pdmlProto) + { + int endPos = -1; + + if (!attributes().value("pos").isEmpty()) + endPos = attributes().value("pos").toString().toInt(); + + pdmlProto->prematureEndHandler(endPos, pbProto, + currentStream_); + pdmlProto->postProtocolHandler(pbProto, currentStream_); + + StreamBase s; + s.protoDataCopyFrom(*currentStream_); + expPos_ = s.frameProtocolLength(0); + } + + readProto(); + + pdmlProto = NULL; + pbProto = NULL; + } + else if (name() == "field") + { + if ((protoName == "fake-field-wrapper") && + (attributes().value("name") == "tcp.segments")) + { + skipElement(); + qDebug("[skipping reassembled tcp segments]"); + + skipUntilEnd_ = true; + continue; + } + + if (pdmlProto == NULL) + { + pdmlProto = appendPdmlProto(protoName, &pbProto); + + qDebug("%s: preProtocolHandler(expPos = %d)", + protoName.toAscii().constData(), expPos_); + pdmlProto->preProtocolHandler(protoName, attributes(), + expPos_, pbProto, currentStream_); + } + + readField(pdmlProto, pbProto); + } + else + skipElement(); + } + } + + // Close-off current protocol + if (pdmlProto) + { + pdmlProto->postProtocolHandler(pbProto, currentStream_); + freePdmlProtocol(pdmlProto); + + StreamBase s; + s.protoDataCopyFrom(*currentStream_); + expPos_ = s.frameProtocolLength(0); + } +} + +void PdmlReader::readField(PdmlProtocol *pdmlProto, + OstProto::Protocol *pbProto) +{ + Q_ASSERT(isStartElement() && name() == "field"); + + // fields with "hide='yes'" are informational and should be skipped + if (attributes().value("hide") == "yes") + { + skipElement(); + return; + } + + QString fieldName = attributes().value("name").toString(); + + qDebug(" fieldName:%s", fieldName.toAscii().constData()); + + pdmlProto->fieldHandler(fieldName, attributes(), pbProto, currentStream_); + + while (!atEnd()) + { + readNext(); + + if (isEndElement()) + break; + + if (isStartElement()) + { + if (name() == "proto") + { + // Since we are in the midst of processing a protocol, we + // end it prematurely before we start processing the + // embedded protocol + // + int endPos = -1; + + if (!attributes().value("pos").isEmpty()) + endPos = attributes().value("pos").toString().toInt(); + + pdmlProto->prematureEndHandler(endPos, pbProto, + currentStream_); + pdmlProto->postProtocolHandler(pbProto, currentStream_); + + StreamBase s; + s.protoDataCopyFrom(*currentStream_); + expPos_ = s.frameProtocolLength(0); + + readProto(); + } + else if (name() == "field") + readField(pdmlProto, pbProto); + else + skipElement(); + } + } +} + +void PdmlReader::appendHexDumpProto(int offset, int size) +{ + OstProto::Protocol *proto = currentStream_->add_protocol(); + OstProto::HexDump *hexDump = proto->MutableExtension(OstProto::hexDump); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kHexDumpFieldNumber); + + qDebug("filling in gap of %d bytes starting from %d", size, offset); + hexDump->set_content(pktBuf_.constData() + offset, size); + hexDump->set_pad_until_end(false); +} + +PdmlProtocol* PdmlReader::appendPdmlProto(const QString &protoName, + OstProto::Protocol **pbProto) +{ + PdmlProtocol* pdmlProto = allocPdmlProtocol(protoName); + Q_ASSERT(pdmlProto != NULL); + + int protoId = pdmlProto->ostProtoId(); + + if (protoId > 0) // Non-Base Class + { + OstProto::Protocol *proto = currentStream_->add_protocol(); + + proto->mutable_protocol_id()->set_id(protoId); + + const google::protobuf::Reflection *msgRefl = proto->GetReflection(); + const google::protobuf::FieldDescriptor *fieldDesc = + msgRefl->FindKnownExtensionByNumber(protoId); + + // TODO: if !fDesc + // init default values of all fields in protocol + msgRefl->MutableMessage(proto, fieldDesc); + + *pbProto = proto; + + qDebug("%s: name = %s", __FUNCTION__, + protoName.toAscii().constData()); + } + else + *pbProto = NULL; + + return pdmlProto; +} diff --git a/common/pdmlreader.h b/common/pdmlreader.h new file mode 100644 index 0000000..7de3918 --- /dev/null +++ b/common/pdmlreader.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PDML_READER_H +#define _PDML_READER_H + +#include "pdmlprotocol.h" + +#include +#include + +class PcapFileFormat; +class PdmlReader : public QObject, public QXmlStreamReader +{ + Q_OBJECT +public: + PdmlReader(OstProto::StreamConfigList *streams); + ~PdmlReader(); + + bool read(QIODevice *device, PcapFileFormat *pcap = NULL, + bool *stop = NULL); +signals: + void progress(int value); + +private: + PdmlProtocol* allocPdmlProtocol(QString protoName); + void freePdmlProtocol(PdmlProtocol *proto); + + bool isDontCareProto(); + void skipElement(); + + void readPdml(); + void readPacket(); + void readProto(); + void readField(PdmlProtocol *pdmlProto, + OstProto::Protocol *pbProto); + + void appendHexDumpProto(int offset, int size); + PdmlProtocol* appendPdmlProto(const QString &protoName, + OstProto::Protocol **pbProto); + + typedef PdmlProtocol* (*FactoryMethod)(); + + QMap factory_; + + bool *stop_; + OstProto::StreamConfigList *streams_; + PcapFileFormat *pcap_; + QByteArray pktBuf_; + + bool isMldSupport_; + int packetCount_; + int expPos_; + bool skipUntilEnd_; + OstProto::Stream *prevStream_; + OstProto::Stream *currentStream_; +}; + +#endif diff --git a/common/protocol.proto b/common/protocol.proto new file mode 100644 index 0000000..bce2b6a --- /dev/null +++ b/common/protocol.proto @@ -0,0 +1,266 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +package OstProto; +option cc_generic_services = true; +option py_generic_services = true; + +message VersionInfo { + required string version = 1; +} + +message VersionCompatibility { + enum Compatibility { + kIncompatible = 0; + kCompatible = 1; + } + required Compatibility result = 1; + optional string notes = 2; +} + +message StreamId { + required uint32 id = 1; +} + +message StreamCore { + enum FrameLengthMode { + e_fl_fixed = 0; + e_fl_inc = 1; + e_fl_dec = 2; + e_fl_random = 3; + } + + // Basics + optional string name = 1; + optional bool is_enabled = 2; + optional uint32 ordinal = 3; + + // Frame Length (includes CRC) + optional FrameLengthMode len_mode = 14 [default = e_fl_fixed]; + optional uint32 frame_len = 15 [default = 64]; + optional uint32 frame_len_min = 16 [default = 64]; + optional uint32 frame_len_max = 17 [default = 1518]; +} + +message StreamControl { + enum SendUnit { + e_su_packets = 0; + e_su_bursts = 1; + } + + enum SendMode { + e_sm_fixed = 0; + e_sm_continuous = 1; + } + + enum NextWhat { + e_nw_stop = 0; + e_nw_goto_next = 1; + e_nw_goto_id = 2; + } + + optional SendUnit unit = 1 [default = e_su_packets]; + optional SendMode mode = 2 [default = e_sm_fixed]; + optional uint32 num_packets = 3 [default = 1]; + optional uint32 num_bursts = 4 [default = 1]; + optional uint32 packets_per_burst = 5 [default = 10]; + optional NextWhat next = 6 [default = e_nw_goto_next]; + optional uint32 OBSOLETE_packets_per_sec = 7 [default = 1, deprecated=true]; + optional uint32 OBSOLETE_bursts_per_sec = 8 [default = 1, deprecated=true]; + optional double packets_per_sec = 9 [default = 1]; + optional double bursts_per_sec = 10 [default = 1]; +} + +message ProtocolId { + required uint32 id = 1; +} + +message Protocol { + + required ProtocolId protocol_id = 1; + + extensions 100 to 199; // Reserved for Ostinato Use + extensions 200 to 500; // Available for use by protocols + + enum k { + kMacFieldNumber = 100; + kPayloadFieldNumber = 101; + kSampleFieldNumber = 102; + kUserScriptFieldNumber = 103; + kHexDumpFieldNumber = 104; + + kEth2FieldNumber = 200; + kDot3FieldNumber = 201; + kLlcFieldNumber = 202; + kSnapFieldNumber = 203; + + kSvlanFieldNumber = 204; + kVlanFieldNumber = 205; + + kDot2LlcFieldNumber = 206; + kDot2SnapFieldNumber = 207; + kVlanStackFieldNumber = 208; + + kArpFieldNumber = 300; + kIp4FieldNumber = 301; + kIp6FieldNumber = 302; + kIp6over4FieldNumber = 303; + kIp4over6FieldNumber = 304; + kIp4over4FieldNumber = 305; + kIp6over6FieldNumber = 306; + + kTcpFieldNumber = 400; + kUdpFieldNumber = 401; + kIcmpFieldNumber = 402; + kIgmpFieldNumber = 403; + kMldFieldNumber = 404; + + kTextProtocolFieldNumber = 500; + } +} + +message Stream { + + required StreamId stream_id = 1; + optional StreamCore core = 2; + optional StreamControl control = 3; + + repeated Protocol protocol = 4; +} + +message Void { + // nothing! +} + +message Ack { + //! \todo (LOW) do we need any fields in 'Ack' +} + +message PortId { + required uint32 id = 1; +} + +message PortIdList { + repeated PortId port_id = 1; +} + +message StreamIdList { + required PortId port_id = 1; + repeated StreamId stream_id = 2; +} + +enum TransmitMode { + kSequentialTransmit = 0; + kInterleavedTransmit = 1; +} + +message Port { + required PortId port_id = 1; + optional string name = 2; + optional string description = 3; + optional string notes = 4; + optional bool is_enabled = 5; + optional bool is_exclusive_control = 6; + optional TransmitMode transmit_mode = 7 [default = kSequentialTransmit]; + optional string user_name = 8; +} + +message PortConfigList { + repeated Port port = 1; +} + +message StreamConfigList { + required PortId port_id = 1; + repeated Stream stream = 2; +} + +message CaptureBuffer { + //! \todo (HIGH) define CaptureBuffer +} + +message CaptureBufferList { + repeated CaptureBuffer list = 1; +} + +enum LinkState { + LinkStateUnknown = 0; + LinkStateDown = 1; + LinkStateUp = 2; +} + +message PortState { + optional LinkState link_state = 1 [default = LinkStateUnknown]; + optional bool is_transmit_on = 2 [default = false]; + optional bool is_capture_on = 3 [default = false]; +} + +message PortStats { + + required PortId port_id = 1; + + optional PortState state = 2; + + optional uint64 rx_pkts = 11; + optional uint64 rx_bytes = 12; + optional uint64 rx_pkts_nic = 13; + optional uint64 rx_bytes_nic = 14; + optional uint64 rx_pps = 15; + optional uint64 rx_bps = 16; + + optional uint64 tx_pkts = 21; + optional uint64 tx_bytes = 22; + optional uint64 tx_pkts_nic = 23; + optional uint64 tx_bytes_nic = 24; + optional uint64 tx_pps = 25; + optional uint64 tx_bps = 26; + + optional uint64 rx_drops = 100; + optional uint64 rx_errors = 101; + optional uint64 rx_fifo_errors = 102; + optional uint64 rx_frame_errors = 103; +} + +message PortStatsList { + repeated PortStats port_stats = 1; +} + +service OstService { + rpc getPortIdList(Void) returns (PortIdList); + rpc getPortConfig(PortIdList) returns (PortConfigList); + rpc modifyPort(PortConfigList) returns (Ack); + + rpc getStreamIdList(PortId) returns (StreamIdList); + rpc getStreamConfig(StreamIdList) returns (StreamConfigList); + rpc addStream(StreamIdList) returns (Ack); + rpc deleteStream(StreamIdList) returns (Ack); + rpc modifyStream(StreamConfigList) returns (Ack); + + rpc startTransmit(PortIdList) returns (Ack); + rpc stopTransmit(PortIdList) returns (Ack); + + rpc startCapture(PortIdList) returns (Ack); + rpc stopCapture(PortIdList) returns (Ack); + rpc getCaptureBuffer(PortId) returns (CaptureBuffer); + + rpc getStats(PortIdList) returns (PortStatsList); + rpc clearStats(PortIdList) returns (Ack); + + rpc checkVersion(VersionInfo) returns (VersionCompatibility); +} + diff --git a/common/protocollist.cpp b/common/protocollist.cpp new file mode 100644 index 0000000..1b3397c --- /dev/null +++ b/common/protocollist.cpp @@ -0,0 +1,27 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "protocollist.h" +#include "abstractprotocol.h" + +void ProtocolList::destroy() +{ + while (!isEmpty()) + delete takeFirst(); +} diff --git a/common/protocollist.h b/common/protocollist.h new file mode 100644 index 0000000..62df3c9 --- /dev/null +++ b/common/protocollist.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include + +class AbstractProtocol; + +class ProtocolList : public QLinkedList +{ +public: + void destroy(); +}; diff --git a/common/protocollistiterator.cpp b/common/protocollistiterator.cpp new file mode 100644 index 0000000..9f82c3d --- /dev/null +++ b/common/protocollistiterator.cpp @@ -0,0 +1,133 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "protocollistiterator.h" +#include "protocollist.h" +#include "abstractprotocol.h" + +ProtocolListIterator::ProtocolListIterator(ProtocolList &list) +{ + _iter = new QMutableLinkedListIterator(list); +} + +ProtocolListIterator::~ProtocolListIterator() +{ + delete _iter; +} + +bool ProtocolListIterator::findNext(const AbstractProtocol* value) const +{ + return _iter->findNext(const_cast(value)); +} + +bool ProtocolListIterator::findPrevious(const AbstractProtocol* value) +{ + return _iter->findPrevious(const_cast(value)); +} + +bool ProtocolListIterator::hasNext() const +{ + return _iter->hasNext(); +} + +bool ProtocolListIterator::hasPrevious() const +{ + return _iter->hasPrevious(); +} + +void ProtocolListIterator::insert(AbstractProtocol* value) +{ + if (_iter->hasPrevious()) + { + value->prev = _iter->peekPrevious(); + value->prev->next = value; + } + else + value->prev = NULL; + + if (_iter->hasNext()) + { + value->next = _iter->peekNext(); + value->next->prev = value; + } + else + value->next = NULL; + + _iter->insert(const_cast(value)); +} + +AbstractProtocol* ProtocolListIterator::next() +{ + return _iter->next(); +} + +AbstractProtocol* ProtocolListIterator::peekNext() const +{ + return _iter->peekNext(); +} + +AbstractProtocol* ProtocolListIterator::peekPrevious() const +{ + return _iter->peekPrevious(); +} + +AbstractProtocol* ProtocolListIterator::previous() +{ + return _iter->previous(); +} + +void ProtocolListIterator::remove() +{ + if (_iter->value()->prev) + _iter->value()->prev->next = _iter->value()->next; + if (_iter->value()->next) + _iter->value()->next->prev = _iter->value()->prev; + _iter->remove(); +} + +void ProtocolListIterator::setValue(AbstractProtocol* value) const +{ + if (_iter->value()->prev) + _iter->value()->prev->next = value; + if (_iter->value()->next) + _iter->value()->next->prev = value; + value->prev = _iter->value()->prev; + value->next = _iter->value()->next; + _iter->setValue(const_cast(value)); +} + +void ProtocolListIterator::toBack() +{ + _iter->toBack(); +} + +void ProtocolListIterator::toFront() +{ + _iter->toFront(); +} + +const AbstractProtocol* ProtocolListIterator::value() const +{ + return _iter->value(); +} + +AbstractProtocol* ProtocolListIterator::value() +{ + return _iter->value(); +} diff --git a/common/protocollistiterator.h b/common/protocollistiterator.h new file mode 100644 index 0000000..6baa39f --- /dev/null +++ b/common/protocollistiterator.h @@ -0,0 +1,48 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include + +class AbstractProtocol; +class ProtocolList; + +class ProtocolListIterator +{ +private: + QMutableLinkedListIterator *_iter; + +public: + ProtocolListIterator(ProtocolList &list); + ~ProtocolListIterator(); + bool findNext(const AbstractProtocol* value) const; + bool findPrevious(const AbstractProtocol* value); + bool hasNext() const; + bool hasPrevious() const; + void insert(AbstractProtocol* value); + AbstractProtocol* next(); + AbstractProtocol* peekNext() const; + AbstractProtocol* peekPrevious() const; + AbstractProtocol* previous(); + void remove(); + void setValue(AbstractProtocol* value) const; + void toBack(); + void toFront(); + const AbstractProtocol* value() const; + AbstractProtocol* value(); +}; diff --git a/common/protocolmanager.cpp b/common/protocolmanager.cpp new file mode 100644 index 0000000..84e1e2d --- /dev/null +++ b/common/protocolmanager.cpp @@ -0,0 +1,232 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "protocolmanager.h" +#include "abstractprotocol.h" + +#include "protocol.pb.h" + +#include "mac.h" +#include "vlan.h" +#include "svlan.h" +#include "vlanstack.h" + +// L2 Protos +#include "dot3.h" +#include "llc.h" +#include "dot2llc.h" +#include "snap.h" +#include "dot2snap.h" +#include "eth2.h" + +// L3 Protos +#include "arp.h" +#include "ip4.h" +#include "ip6.h" +#include "ip4over4.h" +#include "ip4over6.h" +#include "ip6over4.h" +#include "ip6over6.h" + +// L4 Protos +#include "icmp.h" +#include "igmp.h" +#include "mld.h" +#include "tcp.h" +#include "udp.h" + +// L5 Protos +#include "textproto.h" + +// Special Protos +#include "hexdump.h" +#include "payload.h" +#include "sample.h" +#include "userscript.h" + +ProtocolManager *OstProtocolManager; + +ProtocolManager::ProtocolManager() +{ + /*! \todo (LOW) calls to registerProtocol() should be done by the protocols + themselves (once this is done remove the #includes for all the protocols) + */ + registerProtocol(OstProto::Protocol::kMacFieldNumber, + (void*) MacProtocol::createInstance); + + registerProtocol(OstProto::Protocol::kVlanFieldNumber, + (void*) VlanProtocol::createInstance); + registerProtocol(OstProto::Protocol::kSvlanFieldNumber, + (void*) SVlanProtocol::createInstance); + registerProtocol(OstProto::Protocol::kVlanStackFieldNumber, + (void*) VlanStackProtocol::createInstance); + + registerProtocol(OstProto::Protocol::kEth2FieldNumber, + (void*) Eth2Protocol::createInstance); + registerProtocol(OstProto::Protocol::kDot3FieldNumber, + (void*) Dot3Protocol::createInstance); + registerProtocol(OstProto::Protocol::kLlcFieldNumber, + (void*) LlcProtocol::createInstance); + registerProtocol(OstProto::Protocol::kDot2LlcFieldNumber, + (void*) Dot2LlcProtocol::createInstance); + registerProtocol(OstProto::Protocol::kSnapFieldNumber, + (void*) SnapProtocol::createInstance); + registerProtocol(OstProto::Protocol::kDot2SnapFieldNumber, + (void*) Dot2SnapProtocol::createInstance); + + // Layer 3 Protocols + registerProtocol(OstProto::Protocol::kArpFieldNumber, + (void*) ArpProtocol::createInstance); + registerProtocol(OstProto::Protocol::kIp4FieldNumber, + (void*) Ip4Protocol::createInstance); + registerProtocol(OstProto::Protocol::kIp6FieldNumber, + (void*) Ip6Protocol::createInstance); + + registerProtocol(OstProto::Protocol::kIp4over4FieldNumber, + (void*) Ip4over4Protocol::createInstance); + registerProtocol(OstProto::Protocol::kIp4over6FieldNumber, + (void*) Ip4over6Protocol::createInstance); + registerProtocol(OstProto::Protocol::kIp6over4FieldNumber, + (void*) Ip6over4Protocol::createInstance); + registerProtocol(OstProto::Protocol::kIp6over6FieldNumber, + (void*) Ip6over6Protocol::createInstance); + + // Layer 4 Protocols + registerProtocol(OstProto::Protocol::kIcmpFieldNumber, + (void*) IcmpProtocol::createInstance); + registerProtocol(OstProto::Protocol::kIgmpFieldNumber, + (void*) IgmpProtocol::createInstance); + registerProtocol(OstProto::Protocol::kMldFieldNumber, + (void*) MldProtocol::createInstance); + registerProtocol(OstProto::Protocol::kTcpFieldNumber, + (void*) TcpProtocol::createInstance); + registerProtocol(OstProto::Protocol::kUdpFieldNumber, + (void*) UdpProtocol::createInstance); + + // Layer 5 Protocols + registerProtocol(OstProto::Protocol::kTextProtocolFieldNumber, + (void*) TextProtocol::createInstance); + + // Special Protocols + registerProtocol(OstProto::Protocol::kHexDumpFieldNumber, + (void*) HexDumpProtocol::createInstance); + registerProtocol(OstProto::Protocol::kPayloadFieldNumber, + (void*) PayloadProtocol::createInstance); + registerProtocol(OstProto::Protocol::kSampleFieldNumber, + (void*) SampleProtocol::createInstance); + registerProtocol(OstProto::Protocol::kUserScriptFieldNumber, + (void*) UserScriptProtocol::createInstance); + + populateNeighbourProtocols(); +} + +ProtocolManager::~ProtocolManager() +{ + numberToNameMap.clear(); + nameToNumberMap.clear(); + neighbourProtocols.clear(); + factory.clear(); + QList pl = protocolList.values(); + while (!pl.isEmpty()) + delete pl.takeFirst(); +} + +void ProtocolManager::registerProtocol(int protoNumber, + void *protoInstanceCreator) +{ + AbstractProtocol *p; + + Q_ASSERT(!factory.contains(protoNumber)); + + factory.insert(protoNumber, protoInstanceCreator); + + p = createProtocol(protoNumber, NULL); + protocolList.insert(protoNumber, p); + + numberToNameMap.insert(protoNumber, p->shortName()); + nameToNumberMap.insert(p->shortName(), protoNumber); +} + +void ProtocolManager::populateNeighbourProtocols() +{ + neighbourProtocols.clear(); + + foreach(AbstractProtocol *p, protocolList) + { + if (p->protocolIdType() != AbstractProtocol::ProtocolIdNone) + { + foreach(AbstractProtocol *q, protocolList) + { + if (q->protocolId(p->protocolIdType())) + neighbourProtocols.insert( + p->protocolNumber(), q->protocolNumber()); + } + } + } +} + +bool ProtocolManager::isRegisteredProtocol(int protoNumber) +{ + return factory.contains(protoNumber); +} + +AbstractProtocol* ProtocolManager::createProtocol(int protoNumber, + StreamBase *stream, AbstractProtocol *parent) +{ + AbstractProtocol* (*pc)(StreamBase*, AbstractProtocol*); + AbstractProtocol* p; + + pc = (AbstractProtocol* (*)(StreamBase*, AbstractProtocol*)) + factory.value(protoNumber); + + Q_ASSERT_X(pc != NULL, + __FUNCTION__, + QString("No Protocol Creator registered for protocol %1") + .arg(protoNumber).toAscii().constData()); + + p = (*pc)(stream, parent); + + return p; +} + +AbstractProtocol* ProtocolManager::createProtocol(QString protoName, + StreamBase *stream, AbstractProtocol *parent) +{ + return createProtocol(nameToNumberMap.value(protoName), stream, parent); +} + +bool ProtocolManager::isValidNeighbour(int protoPrefix, int protoSuffix) +{ + if (neighbourProtocols.contains(protoPrefix, protoSuffix)) + return true; + else + return false; +} + +bool ProtocolManager::protocolHasPayload(int protoNumber) +{ + Q_ASSERT(protocolList.contains(protoNumber)); + + return protocolList.value(protoNumber)->protocolHasPayload(); +} + +QStringList ProtocolManager::protocolDatabase() +{ + return numberToNameMap.values(); +} diff --git a/common/protocolmanager.h b/common/protocolmanager.h new file mode 100644 index 0000000..ff7279b --- /dev/null +++ b/common/protocolmanager.h @@ -0,0 +1,58 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PROTOCOL_MANAGER_H +#define _PROTOCOL_MANAGER_H + +#include +#include + +class AbstractProtocol; +class StreamBase; + +class ProtocolManager +{ + QMap numberToNameMap; + QMap nameToNumberMap; + QMultiMap neighbourProtocols; + QMap factory; + QMap protocolList; + + void populateNeighbourProtocols(); + +public: + ProtocolManager(); + ~ProtocolManager(); + + // TODO: make registerProtocol static + void registerProtocol(int protoNumber, void *protoInstanceCreator); + + bool isRegisteredProtocol(int protoNumber); + AbstractProtocol* createProtocol(int protoNumber, StreamBase *stream, + AbstractProtocol *parent = 0); + AbstractProtocol* createProtocol(QString protoName, StreamBase *stream, + AbstractProtocol *parent = 0); + + bool isValidNeighbour(int protoPrefix, int protoSuffix); + bool protocolHasPayload(int protoNumber); + + QStringList protocolDatabase(); +}; + +#endif diff --git a/common/protocolwidgetfactory.cpp b/common/protocolwidgetfactory.cpp new file mode 100644 index 0000000..8c8e30a --- /dev/null +++ b/common/protocolwidgetfactory.cpp @@ -0,0 +1,193 @@ +/* +Copyright (C) 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "protocolwidgetfactory.h" + +#include "macconfig.h" +#include "vlanconfig.h" +#include "svlanconfig.h" +#include "vlanstackconfig.h" +// L2 Protocol Widgets +#include "eth2config.h" +#include "dot3config.h" +#include "llcconfig.h" +#include "dot2llcconfig.h" +#include "snapconfig.h" +#include "dot2snapconfig.h" +// L3 Protocol Widgets +#include "arpconfig.h" +#include "ip4config.h" +#include "ip6config.h" +#include "ip4over4config.h" +#include "ip4over6config.h" +#include "ip6over4config.h" +#include "ip6over6config.h" +// L4 Protocol Widgets +#include "icmpconfig.h" +#include "igmpconfig.h" +#include "mldconfig.h" +#include "tcpconfig.h" +#include "udpconfig.h" +// L5 Protocol Widgets +#include "textprotoconfig.h" +// Special Protocol Widgets +#include "hexdumpconfig.h" +#include "payloadconfig.h" +#include "sampleconfig.h" +#include "userscriptconfig.h" + +ProtocolWidgetFactory *OstProtocolWidgetFactory; +QMap ProtocolWidgetFactory::configWidgetFactory; + +ProtocolWidgetFactory::ProtocolWidgetFactory() +{ + /*! + * Ideally Protocol Widgets should register themselves + * with the Factory + */ + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kMacFieldNumber, + (void*) MacConfigForm::createInstance); + + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kVlanFieldNumber, + (void*) VlanConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kSvlanFieldNumber, + (void*) SVlanConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kVlanStackFieldNumber, + (void*) VlanStackConfigForm::createInstance); + + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kEth2FieldNumber, + (void*) Eth2ConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kDot3FieldNumber, + (void*) Dot3ConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kLlcFieldNumber, + (void*) LlcConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kDot2LlcFieldNumber, + (void*) Dot2LlcConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kSnapFieldNumber, + (void*) SnapConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kDot2SnapFieldNumber, + (void*) Dot2SnapConfigForm::createInstance); + + // Layer 3 Protocols + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kArpFieldNumber, + (void*) ArpConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kIp4FieldNumber, + (void*) Ip4ConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kIp6FieldNumber, + (void*) Ip6ConfigForm::createInstance); + + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kIp4over4FieldNumber, + (void*) Ip4over4ConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kIp4over6FieldNumber, + (void*) Ip4over6ConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kIp6over4FieldNumber, + (void*) Ip6over4ConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kIp6over6FieldNumber, + (void*) Ip6over6ConfigForm::createInstance); + + // Layer 4 Protocols + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kIcmpFieldNumber, + (void*) IcmpConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kIgmpFieldNumber, + (void*) IgmpConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kMldFieldNumber, + (void*) MldConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kTcpFieldNumber, + (void*) TcpConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kUdpFieldNumber, + (void*) UdpConfigForm::createInstance); + + // Layer 5 Protocols + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kTextProtocolFieldNumber, + (void*) TextProtocolConfigForm::createInstance); + + // Special Protocols + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kHexDumpFieldNumber, + (void*) HexDumpConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kPayloadFieldNumber, + (void*) PayloadConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kSampleFieldNumber, + (void*) SampleConfigForm::createInstance); + OstProtocolWidgetFactory->registerProtocolConfigWidget( + OstProto::Protocol::kUserScriptFieldNumber, + (void*) UserScriptConfigForm::createInstance); +} + +ProtocolWidgetFactory::~ProtocolWidgetFactory() +{ + configWidgetFactory.clear(); +} + +void ProtocolWidgetFactory::registerProtocolConfigWidget(int protoNumber, + void *protoConfigWidgetInstanceCreator) +{ + Q_ASSERT(!configWidgetFactory.contains(protoNumber)); + + configWidgetFactory.insert(protoNumber, protoConfigWidgetInstanceCreator); +} + +AbstractProtocolConfigForm* ProtocolWidgetFactory::createConfigWidget( + int protoNumber) +{ + AbstractProtocolConfigForm* (*pc)(); + AbstractProtocolConfigForm* p; + + pc = (AbstractProtocolConfigForm* (*)()) + configWidgetFactory.value(protoNumber); + + Q_ASSERT_X(pc != NULL, + __FUNCTION__, + QString(protoNumber).toAscii().constData()); + + p = (*pc)(); + + return p; +} + +void ProtocolWidgetFactory::deleteConfigWidget( + AbstractProtocolConfigForm *configWidget) +{ + delete configWidget; +} diff --git a/common/protocolwidgetfactory.h b/common/protocolwidgetfactory.h new file mode 100644 index 0000000..ebb69ec --- /dev/null +++ b/common/protocolwidgetfactory.h @@ -0,0 +1,46 @@ +/* +Copyright (C) 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PROTOCOL_WIDGET_FACTORY_H +#define _PROTOCOL_WIDGET_FACTORY_H + +#include + +class AbstractProtocolConfigForm; + +// Singleton class +class ProtocolWidgetFactory +{ + static QMap configWidgetFactory; + +public: + ProtocolWidgetFactory(); + ~ProtocolWidgetFactory(); + + // TODO: make registerProtocolConfigWidget static?? + // TODO: define a function pointer prototype instead of void* for + // protoConfigWidgetInstanceCreator + static void registerProtocolConfigWidget(int protoNumber, + void *protoConfigWidgetInstanceCreator); + + AbstractProtocolConfigForm* createConfigWidget(int protoNumber); + void deleteConfigWidget(AbstractProtocolConfigForm *configWidget); +}; + +#endif diff --git a/common/pythonfileformat.cpp b/common/pythonfileformat.cpp new file mode 100644 index 0000000..73f847f --- /dev/null +++ b/common/pythonfileformat.cpp @@ -0,0 +1,553 @@ +/* +Copyright (C) 2015 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pythonfileformat.h" + +#include + +#include +#include + +#include +#include + +using google::protobuf::Message; +using google::protobuf::Reflection; +using google::protobuf::FieldDescriptor; + +PythonFileFormat pythonFileFormat; + +extern char *version; +extern char *revision; + +PythonFileFormat::PythonFileFormat() +{ + // Nothing to do +} + +PythonFileFormat::~PythonFileFormat() +{ + // Nothing to do +} + +bool PythonFileFormat::openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error) +{ + // NOT SUPPORTED! + return false; +} + +bool PythonFileFormat::saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error) +{ + QFile file(fileName); + QTextStream out(&file); + QSet imports; + + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) + goto _open_fail; + + // import standard modules + emit status("Writing imports ..."); + emit target(0); + writeStandardImports(out); + + emit target(streams.stream_size()); + // import protocols from respective modules + // build the import list using a QSet to eliminate duplicates + for (int i = 0; i < streams.stream_size(); i++) { + const OstProto::Stream &stream = streams.stream(i); + for (int j = 0 ; j < stream.protocol_size(); j++) { + const OstProto::Protocol &protocol = stream.protocol(j); + const Reflection *refl = protocol.GetReflection(); + std::vector fields; + + refl->ListFields(protocol, &fields); + for (uint k = 0; k < fields.size(); k++) { + // skip protocol_id field + if (fields.at(k)->number() == + OstProto::Protocol::kProtocolIdFieldNumber) + continue; + + if (fields.at(k)->file()->name() != + fields.at(k)->message_type()->file()->name()) { + imports.insert( + QString("%1 import %2").arg( + QString(fields.at(k)->message_type() + ->file()->name().c_str()) + .replace(".proto", "_pb2"), + fields.at(k)->message_type()->name().c_str())); + imports.insert( + QString("%1 import %2").arg( + QString(fields.at(k) + ->file()->name().c_str()) + .replace(".proto", "_pb2"), + fields.at(k)->name().c_str())); + } + else { + imports.insert( + QString("%1 import %2, %3").arg( + QString(fields.at(k)->file()->name().c_str()) + .replace(".proto", "_pb2"), + fields.at(k)->message_type()->name().c_str(), + fields.at(k)->name().c_str())); + } + } + } + emit progress(i); + } + // write the import statements + out << "# import ostinato modules\n"; + out << "from ostinato.core import DroneProxy, ost_pb\n"; + foreach (QString str, imports) + out << "from ostinato.protocols." << str << "\n"; + out << "\n"; + + // start of script - init, connect to drone etc. + emit status("Writing prologue ..."); + emit target(0); + writePrologue(out); + + // Add streams + emit status("Writing stream adds ..."); + emit target(streams.stream_size()); + out << " # ------------#\n"; + out << " # add streams #\n"; + out << " # ------------#\n"; + out << " stream_id = ost_pb.StreamIdList()\n"; + out << " stream_id.port_id.id = tx_port_number\n"; + for (int i = 0; i < streams.stream_size(); i++) { + out << " stream_id.stream_id.add().id = " + << streams.stream(i).stream_id().id() << "\n"; + emit progress(i); + } + out << " drone.addStream(stream_id)\n"; + out << "\n"; + + // Configure streams with actual values + emit status("Writing stream configuration ..."); + emit target(streams.stream_size()); + out << " # ------------------#\n"; + out << " # configure streams #\n"; + out << " # ------------------#\n"; + out << " stream_cfg = ost_pb.StreamConfigList()\n"; + out << " stream_cfg.port_id.id = tx_port_number\n"; + for (int i = 0; i < streams.stream_size(); i++) { + const OstProto::Stream &stream = streams.stream(i); + const Reflection *refl; + std::vector fields; + + out << "\n"; + out << " # stream " << stream.stream_id().id() << " " + << stream.core().name().c_str() << "\n"; + out << " s = stream_cfg.stream.add()\n"; + out << " s.stream_id.id = " + << stream.stream_id().id() << "\n"; + + // Stream Core values + refl = stream.core().GetReflection(); + refl->ListFields(stream.core(), &fields); + for (uint j = 0; j < fields.size(); j++) { + writeFieldAssignment(out, QString(" s.core.") + .append(fields.at(j)->name().c_str()), + stream.core(), refl, fields.at(j)); + } + + // Stream Control values + refl = stream.control().GetReflection(); + refl->ListFields(stream.control(), &fields); + for (uint j = 0; j < fields.size(); j++) { + writeFieldAssignment(out, QString(" s.control.") + .append(fields.at(j)->name().c_str()), + stream.control(), refl, fields.at(j)); + } + + // Protocols + for (int j = 0 ; j < stream.protocol_size(); j++) { + const OstProto::Protocol &protocol = stream.protocol(j); + + out << "\n" + << " p = s.protocol.add()\n" + << " p.protocol_id.id = " + << QString(OstProto::Protocol_k_descriptor() + ->FindValueByNumber(protocol.protocol_id().id()) + ->full_name().c_str()) + .replace("OstProto", "ost_pb"); + out << "\n"; + refl = protocol.GetReflection(); + refl->ListFields(protocol, &fields); + + for (uint k = 0; k < fields.size(); k++) { + // skip protocol_id field + if (fields.at(k)->number() == + OstProto::Protocol::kProtocolIdFieldNumber) + continue; + QString pfx(" p.Extensions[X]"); + pfx.replace("X", fields.at(k)->name().c_str()); + writeFieldAssignment(out, pfx, protocol, + refl, fields.at(k)); + } + } + emit progress(i); + } + out << "\n"; + out << " drone.modifyStream(stream_cfg)\n"; + + // end of script - transmit streams, disconnect from drone etc. + emit status("Writing epilogue ..."); + emit target(0); + writeEpilogue(out); + + out.flush(); + file.close(); + return true; + +_open_fail: + return false; +} + +bool PythonFileFormat::isMyFileFormat(const QString fileName) +{ + // isMyFileFormat() is used for file open case to detect + // file format - Open not supported for Python Scripts + return false; +} + +bool PythonFileFormat::isMyFileType(const QString fileType) +{ + if (fileType.startsWith("PythonScript")) + return true; + else + return false; +} + +// +// Private Member Functions +// +void PythonFileFormat::writeStandardImports(QTextStream &out) +{ + out << "#! /usr/bin/env python\n"; + out << "\n"; + out << "# This script was programmatically generated\n" + << "# by Ostinato version " << version + << " revision " << revision << "\n" + << "# The script should work out of the box mostly,\n" + << "# but occassionally might need minor tweaking\n" + << "# Please report any bugs at http://ostinato.org\n"; + out << "\n"; + out << "# standard modules\n"; + out << "import logging\n"; + out << "import os\n"; + out << "import sys\n"; + out << "import time\n"; + out << "\n"; +} + +void PythonFileFormat::writePrologue(QTextStream &out) +{ + out << "# initialize the below variables appropriately " + << "to avoid manual input\n"; + out << "host_name = ''\n"; + out << "tx_port_number = -1\n"; + out << "\n"; + out << "# setup logging\n"; + out << "log = logging.getLogger(__name__)\n"; + out << "logging.basicConfig(level=logging.INFO)\n"; + out << "\n"; + out << "# get inputs, if required\n"; + out << "while len(host_name) == 0:\n"; + out << " host_name = raw_input('Drone\\'s Hostname/IP: ')\n"; + out << "while tx_port_number < 0:\n"; + out << " tx_port_number = int(raw_input('Tx Port Number: '))\n"; + out << "\n"; + out << "drone = DroneProxy(host_name)\n"; + out << "\n"; + out << "try:\n"; + out << " # connect to drone\n"; + out << " log.info('connecting to drone(%s:%d)' \n"; + out << " % (drone.hostName(), drone.portNumber()))\n"; + out << " drone.connect()\n"; + out << "\n"; + out << " # setup tx port list\n"; + out << " tx_port = ost_pb.PortIdList()\n"; + out << " tx_port.port_id.add().id = tx_port_number;\n"; + out << "\n"; +} + +void PythonFileFormat::writeEpilogue(QTextStream &out) +{ + out << " # clear tx/rx stats\n"; + out << " log.info('clearing tx stats')\n"; + out << " drone.clearStats(tx_port)\n"; + out << "\n"; + out << " log.info('starting transmit')\n"; + out << " drone.startTransmit(tx_port)\n"; + out << "\n"; + out << " # wait for transmit to finish\n"; + out << " log.info('waiting for transmit to finish ...')\n"; + out << " while True:\n"; + out << " time.sleep(5)\n"; + out << " tx_stats = drone.getStats(tx_port)\n"; + out << " if tx_stats.port_stats[0].state.is_transmit_on == False:\n"; + out << " break\n"; + out << "\n"; + out << " # stop transmit and capture\n"; + out << " log.info('stopping transmit')\n"; + out << " drone.stopTransmit(tx_port)\n"; + out << "\n"; + out << " # get tx stats\n"; + out << " log.info('retreiving stats')\n"; + out << " tx_stats = drone.getStats(tx_port)\n"; + out << "\n"; + out << " log.info('tx pkts = %d' % (tx_stats.port_stats[0].tx_pkts))\n"; + out << "\n"; + out << " # delete streams\n"; + out << " log.info('deleting tx_streams')\n"; + out << " drone.deleteStream(stream_id)\n"; + out << "\n"; + out << " # bye for now\n"; + out << " drone.disconnect()\n"; + out << "\n"; + out << "except Exception as ex:\n"; + out << " log.exception(ex)\n"; + out << " sys.exit(1)\n"; +} + +void PythonFileFormat::writeFieldAssignment( + QTextStream &out, + QString fieldName, + const Message &msg, + const Reflection *refl, + const FieldDescriptor *fieldDesc, + int index) +{ + // for a repeated field, + // if index < 0 => we are writing a repeated aggregate + // if index >= 0 => we are writing a repeated element + if (fieldDesc->is_repeated() && (index < 0)) { + int n = refl->FieldSize(msg, fieldDesc); + QString var = singularize(fieldDesc->name().c_str()); + for (int i = 0; i < n; i++) { + out << " " << var << " = " << fieldName.trimmed() << ".add()\n"; + writeFieldAssignment(out, QString(" ").append(var), + msg, refl, fieldDesc, i); + } + return; + } + + // Ideally fields should not be set if they have the same + // value as the default value - but currently protocols don't + // check this when setting values in the protobuf data object + // so here we check that explicitly for each field and if true + // we don't output anything + switch(fieldDesc->cpp_type()) { + case FieldDescriptor::CPPTYPE_INT32: + { + qint32 val = fieldDesc->is_repeated() ? + refl->GetRepeatedInt32(msg, fieldDesc, index) : + refl->GetInt32(msg, fieldDesc); + if (val != fieldDesc->default_value_int32()) + out << fieldName << " = " << val << "\n"; + break; + } + case FieldDescriptor::CPPTYPE_INT64: + { + qint64 val = fieldDesc->is_repeated() ? + refl->GetRepeatedInt64(msg, fieldDesc, index) : + refl->GetInt64(msg, fieldDesc); + if (val != fieldDesc->default_value_int64()) + out << fieldName << " = " << val << "\n"; + break; + } + case FieldDescriptor::CPPTYPE_UINT32: + { + quint32 val = fieldDesc->is_repeated() ? + refl->GetRepeatedUInt32(msg, fieldDesc, index) : + refl->GetUInt32(msg, fieldDesc); + QString valStr; + + if (useDecimalBase(fieldName)) + valStr.setNum(val); + else + valStr.setNum(val, 16).prepend("0x"); + + if (val != fieldDesc->default_value_uint32()) + out << fieldName << " = " << valStr << "\n"; + break; + } + case FieldDescriptor::CPPTYPE_UINT64: + { + quint64 val = fieldDesc->is_repeated() ? + refl->GetRepeatedUInt64(msg, fieldDesc, index) : + refl->GetUInt64(msg, fieldDesc); + QString valStr; + + if (useDecimalBase(fieldName)) + valStr.setNum(val); + else + valStr.setNum(val, 16).prepend("0x"); + + if (val != fieldDesc->default_value_uint64()) + out << fieldName << " = " << valStr << "\n"; + break; + } + case FieldDescriptor::CPPTYPE_DOUBLE: + { + double val = fieldDesc->is_repeated() ? + refl->GetRepeatedDouble(msg, fieldDesc, index) : + refl->GetDouble(msg, fieldDesc); + if (val != fieldDesc->default_value_double()) + out << fieldName << " = " << val << "\n"; + break; + } + case FieldDescriptor::CPPTYPE_FLOAT: + { + float val = fieldDesc->is_repeated() ? + refl->GetRepeatedFloat(msg, fieldDesc, index) : + refl->GetFloat(msg, fieldDesc); + if (val != fieldDesc->default_value_float()) + out << fieldName << " = " << val << "\n"; + break; + } + case FieldDescriptor::CPPTYPE_BOOL: + { + bool val = fieldDesc->is_repeated() ? + refl->GetRepeatedBool(msg, fieldDesc, index) : + refl->GetBool(msg, fieldDesc); + if (val != fieldDesc->default_value_bool()) + out << fieldName + << " = " + << (refl->GetBool(msg, fieldDesc) ? "True" : "False") + << "\n"; + break; + } + case FieldDescriptor::CPPTYPE_STRING: + { + std::string val = fieldDesc->is_repeated() ? + refl->GetRepeatedStringReference(msg, fieldDesc, index, &val) : + refl->GetStringReference(msg, fieldDesc, &val); + QString escVal = escapeString(val.c_str()); + if (val != fieldDesc->default_value_string()) + out << fieldName << " = '" << escVal << "'\n"; + break; + } + case FieldDescriptor::CPPTYPE_ENUM: + { + // Fields defined in protocol.proto are within ost_pb scope + QString module = fieldDesc->file()->name() == "protocol.proto" ? + "ost_pb." : ""; + std::string val = fieldDesc->is_repeated() ? + refl->GetRepeatedEnum(msg, fieldDesc, index)->full_name() : + refl->GetEnum(msg, fieldDesc)->full_name(); + if (val != fieldDesc->default_value_enum()->full_name()) + out << fieldName << " = " << QString::fromStdString(val) + .replace("OstProto.", module) + << "\n"; + break; + } + case FieldDescriptor::CPPTYPE_MESSAGE: + { + QString pfxStr(fieldName); + const Message &msg2 = fieldDesc->is_repeated() ? + refl->GetRepeatedMessage(msg, fieldDesc, index) : + refl->GetMessage(msg, fieldDesc); + const Reflection *refl2 = msg2.GetReflection(); + std::vector fields2; + QList autoFields; + + refl2->ListFields(msg2, &fields2); + + // Unfortunately, auto-calculated fields such as cksum, length + // and protocol-type etc. may be set in the protobuf even if + // they are not being overridden; + // Intelligence regarding them is inside the respective protocol + // implementation, not inside the protobuf objects - the latter + // is all we have available here to work with; + // We attempt a crude hack here to detect such fields and avoid + // writing assignment statements for them + for (uint i = 0; i < fields2.size(); i++) { + std::string name = fields2.at(i)->name(); + if ((fields2.at(i)->cpp_type() + == FieldDescriptor::CPPTYPE_BOOL) + && (name.find("is_override_") == 0) + && (refl2->GetBool(msg2, fields2.at(i)) == false)) { + name.erase(0, sizeof("is_override_") - 1); + autoFields.append(name); + } + } + + for (uint i = 0 ; i < fields2.size(); i++) { + // skip auto fields that are not overridden + if (autoFields.contains(fields2.at(i)->name())) + continue; + + writeFieldAssignment(out, + QString("%1.%2").arg(pfxStr, + fields2.at(i)->name().c_str()), + msg2, refl2, fields2.at(i)); + } + break; + } + default: + qWarning("unable to write field of unsupported type %d", + fieldDesc->cpp_type()); + } +} + +QString PythonFileFormat::singularize(QString plural) +{ + QString singular = plural; + + // Apply some heuristics + if (plural.endsWith("ies")) + singular.replace(singular.length()-3, 3, "y"); + else if (plural.endsWith("ses")) + singular.chop(2); + else if (plural.endsWith("s")) + singular.chop(1); + + return singular; +} + +QString PythonFileFormat::escapeString(QString str) +{ + QString escStr = ""; + for (int i=0; i < str.length(); i++) { + uchar c = str[i].cell(); + if ((c < 128) && isprint(c)) { + if (c == '\'') + escStr.append("\\'"); + else + escStr.append(str[i]); + } + else + escStr.append(QString("\\x%1").arg(int(c), 2, 16, QChar('0'))); + } + return escStr; +} + +bool PythonFileFormat::useDecimalBase(QString fieldName) +{ + // Heuristic - use Hex base for all except for the following + return fieldName.endsWith("count") + || fieldName.endsWith("length") + || fieldName.endsWith("len") + || fieldName.endsWith("time"); +} + diff --git a/common/pythonfileformat.h b/common/pythonfileformat.h new file mode 100644 index 0000000..55a6452 --- /dev/null +++ b/common/pythonfileformat.h @@ -0,0 +1,59 @@ +/* +Copyright (C) 2015 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PYTHON_FILE_FORMAT_H +#define _PYTHON_FILE_FORMAT_H + +#include "abstractfileformat.h" + +#include + +class PythonFileFormat : public AbstractFileFormat +{ +public: + PythonFileFormat(); + ~PythonFileFormat(); + + virtual bool openStreams(const QString fileName, + OstProto::StreamConfigList &streams, QString &error); + virtual bool saveStreams(const OstProto::StreamConfigList streams, + const QString fileName, QString &error); + + bool isMyFileFormat(const QString fileName); + bool isMyFileType(const QString fileType); + +private: + void writeStandardImports(QTextStream &out); + void writePrologue(QTextStream &out); + void writeEpilogue(QTextStream &out); + void writeFieldAssignment(QTextStream &out, + QString fieldName, + const google::protobuf::Message &msg, + const google::protobuf::Reflection *refl, + const google::protobuf::FieldDescriptor *fieldDesc, + int index = -1); + QString singularize(QString plural); + QString escapeString(QString str); + bool useDecimalBase(QString fieldName); +}; + +extern PythonFileFormat pythonFileFormat; + +#endif + diff --git a/common/sample.cpp b/common/sample.cpp new file mode 100644 index 0000000..0432cf2 --- /dev/null +++ b/common/sample.cpp @@ -0,0 +1,477 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "sample.h" + +SampleProtocol::SampleProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +SampleProtocol::~SampleProtocol() +{ +} + +AbstractProtocol* SampleProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new SampleProtocol(stream, parent); +} + +quint32 SampleProtocol::protocolNumber() const +{ + return OstProto::Protocol::kSampleFieldNumber; +} + +void SampleProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::sample)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void SampleProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::sample)) + data.MergeFrom(protocol.GetExtension(OstProto::sample)); +} + +QString SampleProtocol::name() const +{ + return QString("Sample Protocol"); +} + +QString SampleProtocol::shortName() const +{ + return QString("SAMPLE"); +} + +/*! + TODO Return the ProtocolIdType for your protocol \n + + If your protocol doesn't have a protocolId field, you don't need to + reimplement this method - the base class implementation will do the + right thing +*/ +AbstractProtocol::ProtocolIdType SampleProtocol::protocolIdType() const +{ + return ProtocolIdIp; +} + +/*! + TODO Return the protocolId for your protoocol based on the 'type' requested \n + + If not all types are valid for your protocol, handle the valid type(s) + and for the remaining fallback to the base class implementation; if your + protocol doesn't have a protocolId at all, you don't need to reimplement + this method - the base class will do the right thing +*/ +quint32 SampleProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdLlc: return 0xFFFFFF; + case ProtocolIdEth: return 0xFFFF; + case ProtocolIdIp: return 0xFF; + default:break; + } + + return AbstractProtocol::protocolId(type); +} + +int SampleProtocol::fieldCount() const +{ + return sample_fieldCount; +} + +/*! + TODO Return the number of frame fields for your protocol. A frame field + is a field which has the FrameField flag set \n + + If your protocol has different sets of fields based on a OpCode/Type field + (e.g. icmp), you MUST re-implement this function; however, if your protocol + has a fixed set of frame fields always, you don't need to reimplement this + method - the base class implementation will do the right thing +*/ +int SampleProtocol::frameFieldCount() const +{ + return AbstractProtocol::frameFieldCount(); +} + +/*! + TODO Edit this function to return the appropriate flags for each field \n + + See AbstractProtocol::FieldFlags for more info +*/ +AbstractProtocol::FieldFlags SampleProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case sample_a: + case sample_b: + case sample_payloadLength: + break; + + case sample_checksum: + flags |= CksumField; + break; + + case sample_x: + case sample_y: + break; + + case sample_is_override_checksum: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +/*! +TODO: Edit this function to return the data for each field + +See AbstractProtocol::fieldData() for more info +*/ +QVariant SampleProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case sample_a: + { + int a = data.ab() >> 13; + + switch(attrib) + { + case FieldName: + return QString("A"); + case FieldValue: + return a; + case FieldTextValue: + return QString("%1").arg(a); + case FieldFrameValue: + return QByteArray(1, (char) a); + case FieldBitSize: + return 3; + default: + break; + } + break; + + } + case sample_b: + { + int b = data.ab() & 0x1FFF; + + switch(attrib) + { + case FieldName: + return QString("B"); + case FieldValue: + return b; + case FieldTextValue: + return QString("%1").arg(b, 4, BASE_HEX, QChar('0')); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) b, (uchar*) fv.data()); + return fv; + } + case FieldBitSize: + return 13; + default: + break; + } + break; + } + + case sample_payloadLength: + { + switch(attrib) + { + case FieldName: + return QString("Payload Length"); + case FieldValue: + return protocolFramePayloadSize(streamIndex); + case FieldFrameValue: + { + QByteArray fv; + int totlen; + totlen = protocolFramePayloadSize(streamIndex); + fv.resize(2); + qToBigEndian((quint16) totlen, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QString("%1").arg( + protocolFramePayloadSize(streamIndex)); + case FieldBitSize: + return 16; + default: + break; + } + break; + } + case sample_checksum: + { + quint16 cksum; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_checksum()) + cksum = data.checksum(); + else + cksum = protocolFrameCksum(streamIndex, CksumIp); + break; + default: + cksum = 0; // avoid the 'maybe used unitialized' warning + break; + } + + switch(attrib) + { + case FieldName: + return QString("Checksum"); + case FieldValue: + return cksum; + case FieldFrameValue: + { + QByteArray fv; + + fv.resize(2); + qToBigEndian(cksum, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QString("0x%1").arg( + cksum, 4, BASE_HEX, QChar('0'));; + case FieldBitSize: + return 16; + default: + break; + } + break; + } + case sample_x: + { + switch(attrib) + { + case FieldName: + return QString("X"); + case FieldValue: + return data.x(); + case FieldTextValue: + // Use the following line for display in decimal + return QString("%1").arg(data.x()); + // Use the following line for display in hexa-decimal + //return QString("%1").arg(data.x(), 8, BASE_HEX, QChar('0')); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian((quint32) data.x(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case sample_y: + { + switch(attrib) + { + case FieldName: + return QString("Y"); + case FieldValue: + return data.y(); + case FieldTextValue: + // Use the following line for display in decimal + //return QString("%1").arg(data.y()); + // Use the following line for display in hexa-decimal + return QString("%1").arg(data.y(), 4, BASE_HEX, QChar('0')); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.y(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + + + // Meta fields + case sample_is_override_checksum: + { + switch(attrib) + { + case FieldValue: + return data.is_override_checksum(); + default: + break; + } + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +/*! +TODO: Edit this function to set the data for each field + +See AbstractProtocol::setFieldData() for more info +*/ +bool SampleProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case sample_a: + { + uint a = value.toUInt(&isOk); + if (isOk) + data.set_ab((data.ab() & 0x1FFF) | ((a & 0x07) << 13)); + break; + } + case sample_b: + { + uint b = value.toUInt(&isOk); + if (isOk) + data.set_ab((data.ab() & 0xe000) | (b & 0x1FFF)); + break; + } + case sample_payloadLength: + { + uint len = value.toUInt(&isOk); + if (isOk) + data.set_payload_length(len); + break; + } + case sample_checksum: + { + uint csum = value.toUInt(&isOk); + if (isOk) + data.set_checksum(csum); + break; + } + case sample_x: + { + uint x = value.toUInt(&isOk); + if (isOk) + data.set_x(x); + break; + } + case sample_y: + { + uint y = value.toUInt(&isOk); + if (isOk) + data.set_y(y); + break; + } + case sample_is_override_checksum: + { + bool ovr = value.toBool(); + data.set_is_override_checksum(ovr); + isOk = true; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +/*! + TODO: Return the protocol frame size in bytes\n + + If your protocol has a fixed size - you don't need to reimplement this; the + base class implementation is good enough +*/ +int SampleProtocol::protocolFrameSize(int streamIndex) const +{ + return AbstractProtocol::protocolFrameSize(streamIndex); +} + +/*! + TODO: If your protocol has any variable fields, return true \n + + Otherwise you don't need to reimplement this method - the base class always + returns false +*/ +bool SampleProtocol::isProtocolFrameValueVariable() const +{ + return false; +} + +/*! + TODO: If your protocol frame size can vary across pkts of the same stream, + return true \n + + Otherwise you don't need to reimplement this method - the base class always + returns false +*/ +bool SampleProtocol::isProtocolFrameSizeVariable() const +{ + return false; +} + +/*! + TODO: If your protocol frame has any variable fields or has a variable + size, return the minimum number of frames required to vary the fields \n + + Otherwise you don't need to reimplement this method - the base class always + returns 1 +*/ +int SampleProtocol::protocolFrameVariableCount() const +{ + return 1; +} diff --git a/common/sample.h b/common/sample.h new file mode 100644 index 0000000..475e72f --- /dev/null +++ b/common/sample.h @@ -0,0 +1,89 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SAMPLE_H +#define _SAMPLE_H + +#include "abstractprotocol.h" +#include "sample.pb.h" + +/* +Sample Protocol Frame Format - + +-----+------+------+------+------+------+ + | A | B | LEN | CSUM | X | Y | + | (3) | (13) | (16) | (16) | (32) | (32) | + +-----+------+------+------+------+------+ +Figures in brackets represent field width in bits +*/ + +class SampleProtocol : public AbstractProtocol +{ +public: + enum samplefield + { + // Frame Fields + sample_a = 0, + sample_b, + sample_payloadLength, + sample_checksum, + sample_x, + sample_y, + + // Meta Fields + sample_is_override_checksum, + + sample_fieldCount + }; + + SampleProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~SampleProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual ProtocolIdType protocolIdType() const; + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + virtual int frameFieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual int protocolFrameSize(int streamIndex = 0) const; + + virtual bool isProtocolFrameValueVariable() const; + virtual bool isProtocolFrameSizeVariable() const; + virtual int protocolFrameVariableCount() const; + +private: + OstProto::Sample data; +}; + +#endif diff --git a/common/sample.proto b/common/sample.proto new file mode 100644 index 0000000..eeebfda --- /dev/null +++ b/common/sample.proto @@ -0,0 +1,38 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Sample Protocol +message Sample { + + optional bool is_override_checksum = 1; + + optional uint32 ab = 2; + optional uint32 payload_length = 3; + optional uint32 checksum = 4; + optional uint32 x = 5 [default = 1234]; + optional uint32 y = 6; +} + +extend Protocol { + optional Sample sample = 102; +} diff --git a/common/sample.ui b/common/sample.ui new file mode 100644 index 0000000..2932014 --- /dev/null +++ b/common/sample.ui @@ -0,0 +1,191 @@ + + Sample + + + + 0 + 0 + 263 + 116 + + + + Form + + + + + + Field A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sampleA + + + + + + + >HH; + + + + + + + + + + Checksum + + + + + + + false + + + >HH HH; + + + + + + + Field B + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sampleB + + + + + + + >HH HH; + + + + + + + Field X + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sampleX + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Length + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + samplePayloadLength + + + + + + + false + + + + + + + + + + Field Y + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + sampleY + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + sampleA + sampleB + samplePayloadLength + isChecksumOverride + sampleChecksum + sampleX + sampleY + + + + + isChecksumOverride + toggled(bool) + sampleChecksum + setEnabled(bool) + + + 345 + 122 + + + 406 + 122 + + + + + diff --git a/common/sampleconfig.cpp b/common/sampleconfig.cpp new file mode 100644 index 0000000..caaf0d2 --- /dev/null +++ b/common/sampleconfig.cpp @@ -0,0 +1,117 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "sampleconfig.h" +#include "sample.h" + +SampleConfigForm::SampleConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); +} + +SampleConfigForm::~SampleConfigForm() +{ +} + +SampleConfigForm* SampleConfigForm::createInstance() +{ + return new SampleConfigForm; +} + +/*! +TODO: Edit this function to load each field's data into the config Widget + +See AbstractProtocolConfigForm::loadWidget() for more info +*/ +void SampleConfigForm::loadWidget(AbstractProtocol *proto) +{ + sampleA->setText( + proto->fieldData( + SampleProtocol::sample_a, + AbstractProtocol::FieldValue + ).toString()); + sampleB->setText( + proto->fieldData( + SampleProtocol::sample_b, + AbstractProtocol::FieldValue + ).toString()); + + samplePayloadLength->setText( + proto->fieldData( + SampleProtocol::sample_payloadLength, + AbstractProtocol::FieldValue + ).toString()); + + isChecksumOverride->setChecked( + proto->fieldData( + SampleProtocol::sample_is_override_checksum, + AbstractProtocol::FieldValue + ).toBool()); + sampleChecksum->setText(uintToHexStr( + proto->fieldData( + SampleProtocol::sample_checksum, + AbstractProtocol::FieldValue + ).toUInt(), 2)); + + sampleX->setText( + proto->fieldData( + SampleProtocol::sample_x, + AbstractProtocol::FieldValue + ).toString()); + sampleY->setText( + proto->fieldData( + SampleProtocol::sample_y, + AbstractProtocol::FieldValue + ).toString()); +} + +/*! +TODO: Edit this function to store each field's data from the config Widget + +See AbstractProtocolConfigForm::storeWidget() for more info +*/ +void SampleConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + SampleProtocol::sample_a, + sampleA->text()); + proto->setFieldData( + SampleProtocol::sample_b, + sampleB->text()); + + proto->setFieldData( + SampleProtocol::sample_payloadLength, + samplePayloadLength->text()); + proto->setFieldData( + SampleProtocol::sample_is_override_checksum, + + isChecksumOverride->isChecked()); + proto->setFieldData( + SampleProtocol::sample_checksum, + hexStrToUInt(sampleChecksum->text())); + + proto->setFieldData( + SampleProtocol::sample_x, + sampleX->text()); + proto->setFieldData( + SampleProtocol::sample_y, + sampleY->text()); +} + diff --git a/common/sampleconfig.h b/common/sampleconfig.h new file mode 100644 index 0000000..8e85976 --- /dev/null +++ b/common/sampleconfig.h @@ -0,0 +1,43 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SAMPLE_CONFIG_H +#define _SAMPLE_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_sample.h" + +class SampleConfigForm : + public AbstractProtocolConfigForm, + private Ui::Sample +{ + Q_OBJECT +public: + SampleConfigForm(QWidget *parent = 0); + virtual ~SampleConfigForm(); + + static SampleConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private slots: +}; + +#endif diff --git a/common/samplepdml.cpp b/common/samplepdml.cpp new file mode 100644 index 0000000..99b671b --- /dev/null +++ b/common/samplepdml.cpp @@ -0,0 +1,118 @@ +/* +Copyright (C) 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "samplepdml.h" + +#include "sample.pb.h" + +/*! + TODO : Initialize the following inherited protected members - + - ostProtoId_ + - fieldMap_ + + ostProtoId_ is the protocol's protobuf field number as defined in + message 'Protocol' enum 'k' in file protocol.proto + + fieldMap_ is a mapping of the protocol's field names as they appear + in the PDML to the protobuf field numbers for the protocol. All such + fields are classified as 'known' fields and the base class will take care + of decoding these without any help from the subclass. + + Note that the PDML field names are same as the field names used in Wireshark + display filters. The full reference for these is available at - + http://www.wireshark.org/docs/dfref/ +*/ +PdmlSampleProtocol::PdmlSampleProtocol() +{ + ostProtoId_ = OstProto::Protocol::kSampleFieldNumber; + + fieldMap_.insert("sample.checksum", OstProto::Sample::kChecksumFieldNumber); + fieldMap_.insert("sample.x", OstProto::Sample::kXFieldNumber); + fieldMap_.insert("sample.y", OstProto::Sample::kYFieldNumber); +} + +PdmlSampleProtocol::~PdmlSampleProtocol() +{ +} + +PdmlSampleProtocol* PdmlSampleProtocol::createInstance() +{ + return new PdmlSampleProtocol(); +} + +/*! + TODO: Use this method to do any special handling that may be required for + preprocessing a protocol before parsing/decoding the protocol's fields +*/ +void PdmlSampleProtocol::preProtocolHandler(QString /*name*/, + const QXmlStreamAttributes& /*attributes*/, + int /*expectedPos*/, OstProto::Protocol* /*pbProto*/, + OstProto::Stream* /*stream*/) +{ + return; +} + +/*! + TODO: Use this method to do any special handling or cleanup that may be + required when a protocol decode is ending prematurely +*/ +void PdmlSampleProtocol::prematureEndHandler(int /*pos*/, + OstProto::Protocol* /*pbProto*/, OstProto::Stream* /*stream*/) +{ + return; +} + +/*! + TODO: Use this method to do any special handling that may be required for + postprocessing a protocol after parsing/decoding all the protocol fields + + If your protocol's protobuf has some meta-fields that should be set to + their non default values, this is a good place to do that. e.g. derived + fields such as length, checksum etc. may be correct or incorrect in the + PCAP/PDML - to retain the same value as in the PCAP/PDML and not let + Ostinato recalculate these, you can set the is_override_length, + is_override_cksum meta-fields to true here +*/ +void PdmlSampleProtocol::postProtocolHandler(OstProto::Protocol* /*pbProto*/, + OstProto::Stream* /*stream*/) +{ + return; +} + +/*! + TODO: Handle all 'unknown' fields using this method + + You need to typically only handle frame fields or fields actually present + in the protocol on the wire. So you can safely ignore meta-fields such as + Good/Bad Checksum. + + Some fields may not have a 'name' attribute, so cannot be classified as + a 'known' field. Use this method to identify such fields using other + attributes such as 'show' or 'showname' and populate the corresponding + protobuf field. + + If the PDML protocol contains some fields that are not supported by Ostinato, + use a HexDump protocol as a replacement to store these bytes +*/ +void PdmlSampleProtocol::unknownFieldHandler(QString /*name*/, + int /*pos*/, int /*size*/, const QXmlStreamAttributes& /*attributes*/, + OstProto::Protocol* /*pbProto*/, OstProto::Stream* /*stream*/) +{ + return; +} diff --git a/common/samplepdml.h b/common/samplepdml.h new file mode 100644 index 0000000..a07965b --- /dev/null +++ b/common/samplepdml.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SAMPLE_PDML_H +#define _SAMPLE_PDML_H + +#include "pdmlprotocol.h" + +class PdmlSampleProtocol : public PdmlProtocol +{ +public: + virtual ~PdmlSampleProtocol(); + + static PdmlSampleProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void prematureEndHandler(int pos, OstProto::Protocol *pbProto, + OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); + + void fieldHandler(QString name, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + +protected: + PdmlSampleProtocol(); +}; + +#endif diff --git a/common/snap.cpp b/common/snap.cpp new file mode 100644 index 0000000..6e7e7cc --- /dev/null +++ b/common/snap.cpp @@ -0,0 +1,255 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "snap.h" + +quint32 kStdOui = 0x000000; + +SnapProtocol::SnapProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +SnapProtocol::~SnapProtocol() +{ +} + +AbstractProtocol* SnapProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new SnapProtocol(stream, parent); +} + +quint32 SnapProtocol::protocolNumber() const +{ + return OstProto::Protocol::kSnapFieldNumber; +} + +void SnapProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::snap)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void SnapProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::snap)) + data.MergeFrom(protocol.GetExtension(OstProto::snap)); +} + +QString SnapProtocol::name() const +{ + return QString("SubNetwork Access Protocol"); +} + +QString SnapProtocol::shortName() const +{ + return QString("SNAP"); +} + +AbstractProtocol::ProtocolIdType SnapProtocol::protocolIdType() const +{ + return ProtocolIdEth; +} + +quint32 SnapProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdLlc: return 0xAAAA03; + default: break; + } + + return AbstractProtocol::protocolId(type); +} + +int SnapProtocol::fieldCount() const +{ + return snap_fieldCount; +} + +AbstractProtocol::FieldFlags SnapProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case snap_oui: + case snap_type: + break; + + case snap_is_override_oui: + case snap_is_override_type: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + break; + } + + return flags; +} + +QVariant SnapProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case snap_oui: + switch(attrib) + { + case FieldName: + return QString("OUI"); + case FieldValue: + { + quint32 oui = data.is_override_oui() ? data.oui() : kStdOui; + return oui; + } + case FieldTextValue: + { + quint32 oui = data.is_override_oui() ? data.oui() : kStdOui; + return QString("%1").arg(oui, 6, BASE_HEX, QChar('0')); + } + case FieldFrameValue: + { + quint32 oui = data.is_override_oui() ? data.oui() : kStdOui; + QByteArray fv; + fv.resize(4); + qToBigEndian(oui, (uchar*) fv.data()); + fv.remove(0, 1); + return fv; + } + default: + break; + } + break; + case snap_type: + { + quint16 type; + + switch(attrib) + { + case FieldName: + return QString("Type"); + case FieldValue: + type = data.is_override_type() ? + data.type() : payloadProtocolId(ProtocolIdEth); + return type; + case FieldTextValue: + type = data.is_override_type() ? + data.type() : payloadProtocolId(ProtocolIdEth); + return QString("%1").arg(type, 4, BASE_HEX, QChar('0')); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + type = data.is_override_type() ? + data.type() : payloadProtocolId(ProtocolIdEth); + qToBigEndian(type, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + + // Meta fields + case snap_is_override_oui: + { + switch(attrib) + { + case FieldValue: + return data.is_override_oui(); + default: + break; + } + break; + } + case snap_is_override_type: + { + switch(attrib) + { + case FieldValue: + return data.is_override_type(); + default: + break; + } + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool SnapProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + return false; + + switch (index) + { + case snap_oui: + { + uint oui = value.toUInt(&isOk); + if (isOk) + data.set_oui(oui); + break; + } + case snap_type: + { + uint type = value.toUInt(&isOk); + if (isOk) + data.set_type(type); + break; + } + case snap_is_override_oui: + { + bool ovr = value.toBool(); + data.set_is_override_oui(ovr); + isOk = true; + break; + } + case snap_is_override_type: + { + bool ovr = value.toBool(); + data.set_is_override_type(ovr); + isOk = true; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + return isOk; + +} diff --git a/common/snap.h b/common/snap.h new file mode 100644 index 0000000..bf3a349 --- /dev/null +++ b/common/snap.h @@ -0,0 +1,70 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SNAP_H +#define _SNAP_H + +#include "abstractprotocol.h" + +#include "snap.pb.h" + +class SnapProtocol : public AbstractProtocol +{ +public: + enum snapfield + { + snap_oui = 0, + snap_type, + + // Meta fields + snap_is_override_oui, + snap_is_override_type, + + snap_fieldCount + }; + + SnapProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~SnapProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual ProtocolIdType protocolIdType() const; + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + +private: + OstProto::Snap data; +}; + +#endif diff --git a/common/snap.proto b/common/snap.proto new file mode 100644 index 0000000..26c607c --- /dev/null +++ b/common/snap.proto @@ -0,0 +1,34 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +message Snap { + optional bool is_override_oui = 3; + optional bool is_override_type = 4; + + optional uint32 oui = 1; + optional uint32 type = 2; +} + +extend Protocol { + optional Snap snap = 203; +} diff --git a/common/snap.ui b/common/snap.ui new file mode 100644 index 0000000..374c7a8 --- /dev/null +++ b/common/snap.ui @@ -0,0 +1,122 @@ + + snap + + + + 0 + 0 + 268 + 98 + + + + Form + + + + + + SNAP + + + + + + OUI + + + + + + + false + + + >HH HH HH; + + + + + + + Type + + + + + + + false + + + >HH HH; + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + cbOverrideOui + toggled(bool) + leOui + setEnabled(bool) + + + 49 + 42 + + + 68 + 43 + + + + + cbOverrideType + toggled(bool) + leType + setEnabled(bool) + + + 161 + 34 + + + 183 + 33 + + + + + diff --git a/common/snapconfig.cpp b/common/snapconfig.cpp new file mode 100644 index 0000000..e594b57 --- /dev/null +++ b/common/snapconfig.cpp @@ -0,0 +1,78 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "snapconfig.h" +#include "snap.h" + +SnapConfigForm::SnapConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); +} + +SnapConfigForm::~SnapConfigForm() +{ +} + +SnapConfigForm* SnapConfigForm::createInstance() +{ + return new SnapConfigForm; +} + +void SnapConfigForm::loadWidget(AbstractProtocol *proto) +{ + cbOverrideOui->setChecked( + proto->fieldData( + SnapProtocol::snap_is_override_oui, + AbstractProtocol::FieldValue + ).toBool()); + leOui->setText(uintToHexStr( + proto->fieldData( + SnapProtocol::snap_oui, + AbstractProtocol::FieldValue + ).toUInt(), 3)); + + cbOverrideType->setChecked( + proto->fieldData( + SnapProtocol::snap_is_override_type, + AbstractProtocol::FieldValue + ).toBool()); + leType->setText(uintToHexStr( + proto->fieldData( + SnapProtocol::snap_type, + AbstractProtocol::FieldValue + ).toUInt(), 2)); +} + +void SnapConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + SnapProtocol::snap_is_override_oui, + cbOverrideOui->isChecked()); + proto->setFieldData( + SnapProtocol::snap_oui, + hexStrToUInt(leOui->text())); + + proto->setFieldData( + SnapProtocol::snap_is_override_type, + cbOverrideType->isChecked()); + proto->setFieldData( + SnapProtocol::snap_type, + hexStrToUInt(leType->text())); +} diff --git a/common/snapconfig.h b/common/snapconfig.h new file mode 100644 index 0000000..9d230e3 --- /dev/null +++ b/common/snapconfig.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SNAP_CONFIG_H +#define _SNAP_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_snap.h" + +class SnapConfigForm : + public AbstractProtocolConfigForm, + private Ui::snap +{ + Q_OBJECT +public: + SnapConfigForm(QWidget *parent = 0); + virtual ~SnapConfigForm(); + + static SnapConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +}; + +#endif diff --git a/common/streambase.cpp b/common/streambase.cpp new file mode 100644 index 0000000..e27b233 --- /dev/null +++ b/common/streambase.cpp @@ -0,0 +1,567 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "streambase.h" +#include "abstractprotocol.h" +#include "protocollist.h" +#include "protocollistiterator.h" +#include "protocolmanager.h" + +extern ProtocolManager *OstProtocolManager; + +StreamBase::StreamBase() : + mStreamId(new OstProto::StreamId), + mCore(new OstProto::StreamCore), + mControl(new OstProto::StreamControl) +{ + AbstractProtocol *proto; + ProtocolListIterator *iter; + + mStreamId->set_id(0xFFFFFFFF); + + currentFrameProtocols = new ProtocolList; + + iter = createProtocolListIterator(); + // By default newly created streams have the mac and payload protocols + proto = OstProtocolManager->createProtocol( + OstProto::Protocol::kMacFieldNumber, this); + iter->insert(proto); + qDebug("stream: mac = %p", proto); + + proto = OstProtocolManager->createProtocol( + OstProto::Protocol::kPayloadFieldNumber, this); + iter->insert(proto); + qDebug("stream: payload = %p", proto); + +#ifndef QT_NO_DEBUG_OUTPUT + { + iter->toFront(); + while (iter->hasNext()) + { + qDebug("{{%p}}", iter->next()); + // qDebug("{{%p}: %d}", iter->peekNext(), iter->next()->protocolNumber()); + } + iter->toFront(); + while (iter->hasNext()) + { + qDebug("{[%d]}", iter->next()->protocolNumber()); + // qDebug("{{%p}: %d}", iter->peekNext(), iter->next()->protocolNumber()); + } + } +#endif + + delete iter; +} + +StreamBase::~StreamBase() +{ + currentFrameProtocols->destroy(); + delete currentFrameProtocols; + delete mControl; + delete mCore; + delete mStreamId; +} + +void StreamBase::protoDataCopyFrom(const OstProto::Stream &stream) +{ + AbstractProtocol *proto; + ProtocolListIterator *iter; + + mStreamId->CopyFrom(stream.stream_id()); + mCore->CopyFrom(stream.core()); + mControl->CopyFrom(stream.control()); + + currentFrameProtocols->destroy(); + iter = createProtocolListIterator(); + for (int i=0; i < stream.protocol_size(); i++) + { + int protoId = stream.protocol(i).protocol_id().id(); + + if (!OstProtocolManager->isRegisteredProtocol(protoId)) + { + qWarning("Skipping unregistered protocol %d", protoId); + continue; + } + proto = OstProtocolManager->createProtocol(protoId, this); + proto->protoDataCopyFrom(stream.protocol(i)); + iter->insert(proto); + } + + delete iter; +} + +void StreamBase::protoDataCopyInto(OstProto::Stream &stream) const +{ + stream.mutable_stream_id()->CopyFrom(*mStreamId); + stream.mutable_core()->CopyFrom(*mCore); + stream.mutable_control()->CopyFrom(*mControl); + + stream.clear_protocol(); + foreach (const AbstractProtocol* proto, *currentFrameProtocols) + { + OstProto::Protocol *p; + + p = stream.add_protocol(); + proto->protoDataCopyInto(*p); + } +} + +#if 0 +ProtocolList StreamBase::frameProtocol() +{ + return currentFrameProtocols; +} + +void StreamBase::setFrameProtocol(ProtocolList protocolList) +{ + //currentFrameProtocols.destroy(); + currentFrameProtocols = protocolList; +} +#endif + +ProtocolListIterator* StreamBase::createProtocolListIterator() const +{ + return new ProtocolListIterator(*currentFrameProtocols); +} + +quint32 StreamBase::id() +{ + return mStreamId->id(); +} + +bool StreamBase::setId(quint32 id) +{ + mStreamId->set_id(id); + return true; +} + +quint32 StreamBase::ordinal() +{ + return mCore->ordinal(); +} + +bool StreamBase::setOrdinal(quint32 ordinal) +{ + mCore->set_ordinal(ordinal); + return true; +} + +bool StreamBase::isEnabled() const +{ + return mCore->is_enabled(); +} + +bool StreamBase::setEnabled(bool flag) +{ + mCore->set_is_enabled(flag); + return true; +} + +const QString StreamBase::name() const +{ + return QString().fromStdString(mCore->name()); +} + +bool StreamBase::setName(QString name) +{ + mCore->set_name(name.toStdString()); + return true; +} + +StreamBase::FrameLengthMode StreamBase::lenMode() const +{ + return (StreamBase::FrameLengthMode) mCore->len_mode(); +} + +bool StreamBase::setLenMode(FrameLengthMode lenMode) +{ + mCore->set_len_mode((OstProto::StreamCore::FrameLengthMode) lenMode); + return true; +} + +quint16 StreamBase::frameLen(int streamIndex) const +{ + int pktLen; + + // Decide a frame length based on length mode + switch(lenMode()) + { + case OstProto::StreamCore::e_fl_fixed: + pktLen = mCore->frame_len(); + break; + case OstProto::StreamCore::e_fl_inc: + pktLen = frameLenMin() + (streamIndex % + (frameLenMax() - frameLenMin() + 1)); + break; + case OstProto::StreamCore::e_fl_dec: + pktLen = frameLenMax() - (streamIndex % + (frameLenMax() - frameLenMin() + 1)); + break; + case OstProto::StreamCore::e_fl_random: + //! \todo (MED) This 'random' sequence is same across iterations + pktLen = 64; // to avoid the 'maybe used uninitialized' warning + qsrand(reinterpret_cast(this)); + for (int i = 0; i <= streamIndex; i++) + pktLen = qrand(); + pktLen = frameLenMin() + (pktLen % + (frameLenMax() - frameLenMin() + 1)); + break; + default: + qWarning("Unhandled len mode %d. Using default 64", + lenMode()); + pktLen = 64; + break; + } + + return pktLen; +} + +bool StreamBase::setFrameLen(quint16 frameLen) +{ + mCore->set_frame_len(frameLen); + return true; +} + +quint16 StreamBase::frameLenMin() const +{ + return mCore->frame_len_min(); +} + +bool StreamBase::setFrameLenMin(quint16 frameLenMin) +{ + mCore->set_frame_len_min(frameLenMin); + return true; +} + +quint16 StreamBase::frameLenMax() const +{ + return mCore->frame_len_max(); +} + +bool StreamBase::setFrameLenMax(quint16 frameLenMax) +{ + mCore->set_frame_len_max(frameLenMax); + return true; +} + +/*! Convenience Function */ +quint16 StreamBase::frameLenAvg() const +{ + quint16 avgFrameLen; + + if (lenMode() == e_fl_fixed) + avgFrameLen = frameLen(); + else + avgFrameLen = (frameLenMin() + frameLenMax())/2; + + return avgFrameLen; +} + +StreamBase::SendUnit StreamBase::sendUnit() const +{ + return (StreamBase::SendUnit) mControl->unit(); +} + +bool StreamBase::setSendUnit(SendUnit sendUnit) +{ + mControl->set_unit((OstProto::StreamControl::SendUnit) sendUnit); + return true; +} + +StreamBase::SendMode StreamBase::sendMode() const +{ + return (StreamBase::SendMode) mControl->mode(); +} + +bool StreamBase::setSendMode(SendMode sendMode) +{ + mControl->set_mode( + (OstProto::StreamControl::SendMode) sendMode); + return true; +} + +StreamBase::NextWhat StreamBase::nextWhat() const +{ + return (StreamBase::NextWhat) mControl->next(); +} + +bool StreamBase::setNextWhat(NextWhat nextWhat) +{ + mControl->set_next((OstProto::StreamControl::NextWhat) nextWhat); + return true; +} + +quint32 StreamBase::numPackets() const +{ + return (quint32) mControl->num_packets(); +} + +bool StreamBase::setNumPackets(quint32 numPackets) +{ + mControl->set_num_packets(numPackets); + return true; +} + +quint32 StreamBase::numBursts() const +{ + return (quint32) mControl->num_bursts(); +} + +bool StreamBase::setNumBursts(quint32 numBursts) +{ + mControl->set_num_bursts(numBursts); + return true; +} + +quint32 StreamBase::burstSize() const +{ + return (quint32) mControl->packets_per_burst(); +} + +bool StreamBase::setBurstSize(quint32 packetsPerBurst) +{ + mControl->set_packets_per_burst(packetsPerBurst); + return true; +} + +double StreamBase::packetRate() const +{ + return (double) mControl->packets_per_sec(); +} + +bool StreamBase::setPacketRate(double packetsPerSec) +{ + mControl->set_packets_per_sec(packetsPerSec); + return true; +} + +double StreamBase::burstRate() const +{ + return (double) mControl->bursts_per_sec(); +} + +bool StreamBase::setBurstRate(double burstsPerSec) +{ + mControl->set_bursts_per_sec(burstsPerSec); + return true; +} + +/*! Convenience Function */ +double StreamBase::averagePacketRate() const +{ + double avgPacketRate = 0; + + switch (sendUnit()) + { + case e_su_bursts: + avgPacketRate = burstRate() * burstSize(); + break; + case e_su_packets: + avgPacketRate = packetRate(); + break; + default: + Q_ASSERT(false); // Unreachable!! + } + + return avgPacketRate; +} + +/*! Convenience Function */ +bool StreamBase::setAveragePacketRate(double packetsPerSec) +{ + switch (sendUnit()) + { + case e_su_bursts: + setBurstRate(packetsPerSec/double(burstSize())); + break; + case e_su_packets: + setPacketRate(packetsPerSec); + break; + default: + Q_ASSERT(false); // Unreachable!! + } + + return true; +} + +bool StreamBase::isFrameVariable() const +{ + ProtocolListIterator *iter; + + iter = createProtocolListIterator(); + while (iter->hasNext()) + { + AbstractProtocol *proto; + + proto = iter->next(); + if (proto->isProtocolFrameValueVariable()) + goto _exit; + } + delete iter; + return false; + +_exit: + delete iter; + return true; +} + +bool StreamBase::isFrameSizeVariable() const +{ + ProtocolListIterator *iter; + + iter = createProtocolListIterator(); + while (iter->hasNext()) + { + AbstractProtocol *proto; + + proto = iter->next(); + if (proto->isProtocolFrameSizeVariable()) + goto _exit; + } + delete iter; + return false; + +_exit: + delete iter; + return true; +} + +int StreamBase::frameVariableCount() const +{ + ProtocolListIterator *iter; + quint64 frameCount = 1; + + iter = createProtocolListIterator(); + while (iter->hasNext()) + { + AbstractProtocol *proto; + int count; + + proto = iter->next(); + count = proto->protocolFrameVariableCount(); + + // correct count for mis-behaving protocols + if (count <= 0) + count = 1; + + frameCount = AbstractProtocol::lcm(frameCount, count); + } + delete iter; + + return frameCount; +} + +// frameProtocolLength() returns the sum of all the individual protocol sizes +// which may be different from frameLen() +int StreamBase::frameProtocolLength(int frameIndex) const +{ + int len = 0; + ProtocolListIterator *iter = createProtocolListIterator(); + + while (iter->hasNext()) + { + AbstractProtocol *proto = iter->next(); + + len += proto->protocolFrameSize(frameIndex); + } + delete iter; + + return len; +} + +int StreamBase::frameCount() const +{ + int count = 0; + + switch (sendUnit()) + { + case e_su_packets: count = numPackets(); break; + case e_su_bursts: count = numBursts() * burstSize(); break; + default: Q_ASSERT(false); // unreachable + } + + return count; +} + +int StreamBase::frameValue(uchar *buf, int bufMaxSize, int frameIndex) const +{ + int pktLen, len = 0; + + pktLen = frameLen(frameIndex); + + // pktLen is adjusted for CRC/FCS which will be added by the NIC + pktLen -= kFcsSize; + + if ((pktLen < 0) || (pktLen > bufMaxSize)) + return 0; + + ProtocolListIterator *iter; + + iter = createProtocolListIterator(); + while (iter->hasNext()) + { + AbstractProtocol *proto; + QByteArray ba; + + proto = iter->next(); + ba = proto->protocolFrameValue(frameIndex); + + if (len + ba.size() < bufMaxSize) + memcpy(buf+len, ba.constData(), ba.size()); + len += ba.size(); + } + delete iter; + + // Pad with zero, if required + if (len < pktLen) + memset(buf+len, 0, pktLen-len); + + return pktLen; +} + +bool StreamBase::preflightCheck(QString &result) const +{ + bool pass = true; + int count = isFrameSizeVariable() ? frameCount() : 1; + + for (int i = 0; i < count; i++) + { + if (frameLen(i) < (frameProtocolLength(i) + kFcsSize)) + { + result += QString("One or more frames may be truncated - " + "frame length should be at least %1.\n") + .arg(frameProtocolLength(i) + kFcsSize); + pass = false; + } + + if (frameLen(i) > 1522) + { + result += QString("Jumbo frames may be truncated or dropped " + "if not supported by the hardware\n"); + pass = false; + } + } + + return pass; +} + +bool StreamBase::StreamLessThan(StreamBase* stream1, StreamBase* stream2) +{ + return stream1->ordinal() < stream2->ordinal() ? true : false; +} diff --git a/common/streambase.h b/common/streambase.h new file mode 100644 index 0000000..9ef3ba1 --- /dev/null +++ b/common/streambase.h @@ -0,0 +1,150 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _STREAM_BASE_H +#define _STREAM_BASE_H + +#include +#include + +#include "protocol.pb.h" + +const int kFcsSize = 4; + +class AbstractProtocol; +class ProtocolList; +class ProtocolListIterator; + +class StreamBase +{ +private: + OstProto::StreamId *mStreamId; + OstProto::StreamCore *mCore; + OstProto::StreamControl *mControl; + + ProtocolList *currentFrameProtocols; + +public: + StreamBase(); + ~StreamBase(); + + void protoDataCopyFrom(const OstProto::Stream &stream); + void protoDataCopyInto(OstProto::Stream &stream) const; + + ProtocolListIterator* createProtocolListIterator() const; + + //! \todo (LOW) should we have a copy constructor?? + +public: + enum FrameLengthMode { + e_fl_fixed, + e_fl_inc, + e_fl_dec, + e_fl_random + }; + + enum SendUnit { + e_su_packets, + e_su_bursts + }; + + enum SendMode { + e_sm_fixed, + e_sm_continuous + }; + + enum NextWhat { + e_nw_stop, + e_nw_goto_next, + e_nw_goto_id + }; + + quint32 id(); + bool setId(quint32 id); + +#if 0 // FIXME(HI): needed? + quint32 portId() + { return mCore->port_id();} + bool setPortId(quint32 id) + { mCore->set_port_id(id); return true;} +#endif + + quint32 ordinal(); + bool setOrdinal(quint32 ordinal); + + bool isEnabled() const; + bool setEnabled(bool flag); + + const QString name() const ; + bool setName(QString name) ; + + // Frame Length (includes FCS); + FrameLengthMode lenMode() const; + bool setLenMode(FrameLengthMode lenMode); + + quint16 frameLen(int streamIndex = 0) const; + bool setFrameLen(quint16 frameLen); + + quint16 frameLenMin() const; + bool setFrameLenMin(quint16 frameLenMin); + + quint16 frameLenMax() const; + bool setFrameLenMax(quint16 frameLenMax); + + quint16 frameLenAvg() const; + + SendUnit sendUnit() const; + bool setSendUnit(SendUnit sendUnit); + + SendMode sendMode() const; + bool setSendMode(SendMode sendMode); + + NextWhat nextWhat() const; + bool setNextWhat(NextWhat nextWhat); + + quint32 numPackets() const; + bool setNumPackets(quint32 numPackets); + + quint32 numBursts() const; + bool setNumBursts(quint32 numBursts); + + quint32 burstSize() const; + bool setBurstSize(quint32 packetsPerBurst); + + double packetRate() const; + bool setPacketRate(double packetsPerSec); + + double burstRate() const; + bool setBurstRate(double burstsPerSec); + + double averagePacketRate() const; + bool setAveragePacketRate(double packetsPerSec); + + bool isFrameVariable() const; + bool isFrameSizeVariable() const; + int frameVariableCount() const; + int frameProtocolLength(int frameIndex) const; + int frameCount() const; + int frameValue(uchar *buf, int bufMaxSize, int frameIndex) const; + bool preflightCheck(QString &result) const; + + static bool StreamLessThan(StreamBase* stream1, StreamBase* stream2); +}; + +#endif diff --git a/common/svlan.cpp b/common/svlan.cpp new file mode 100644 index 0000000..9b5f65b --- /dev/null +++ b/common/svlan.cpp @@ -0,0 +1,66 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "svlan.h" +#include "svlan.pb.h" + +SVlanProtocol::SVlanProtocol(StreamBase *stream, AbstractProtocol *parent) + : VlanProtocol(stream, parent) +{ + data.set_tpid(0x88a8); + data.set_is_override_tpid(true); +} + +SVlanProtocol::~SVlanProtocol() +{ +} + +AbstractProtocol* SVlanProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new SVlanProtocol(stream, parent); +} + +quint32 SVlanProtocol::protocolNumber() const +{ + return OstProto::Protocol::kSvlanFieldNumber; +} + +void SVlanProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::svlan)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void SVlanProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::svlan)) + data.MergeFrom(protocol.GetExtension(OstProto::svlan)); +} + +QString SVlanProtocol::name() const +{ + return QString("SVlan"); +} + +QString SVlanProtocol::shortName() const +{ + return QString("SVlan"); +} diff --git a/common/svlan.h b/common/svlan.h new file mode 100644 index 0000000..7ba051b --- /dev/null +++ b/common/svlan.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SVLAN_H +#define _SVLAN_H + +#include "vlan.h" + +class SVlanProtocol : public VlanProtocol +{ +public: + SVlanProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~SVlanProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; +}; + +#endif diff --git a/common/svlan.proto b/common/svlan.proto new file mode 100644 index 0000000..937a9c1 --- /dev/null +++ b/common/svlan.proto @@ -0,0 +1,27 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; +import "vlan.proto"; + +package OstProto; + +extend Protocol { + optional Vlan svlan = 204; +} diff --git a/common/svlanconfig.h b/common/svlanconfig.h new file mode 100644 index 0000000..e5bd578 --- /dev/null +++ b/common/svlanconfig.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SVLAN_CONFIG_H +#define _SVLAN_CONFIG_H + +#include "vlanconfig.h" + +typedef VlanConfigForm SVlanConfigForm; + +#endif + diff --git a/common/svlanpdml.cpp b/common/svlanpdml.cpp new file mode 100644 index 0000000..113f515 --- /dev/null +++ b/common/svlanpdml.cpp @@ -0,0 +1,110 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "svlanpdml.h" + +#include "eth2.pb.h" +#include "svlan.pb.h" + +PdmlSvlanProtocol::PdmlSvlanProtocol() +{ + ostProtoId_ = OstProto::Protocol::kSvlanFieldNumber; +} + +PdmlProtocol* PdmlSvlanProtocol::createInstance() +{ + return new PdmlSvlanProtocol(); +} + +void PdmlSvlanProtocol::preProtocolHandler(QString /*name*/, + const QXmlStreamAttributes& /*attributes*/, int /*expectedPos*/, + OstProto::Protocol *pbProto, OstProto::Stream *stream) +{ + OstProto::Vlan *svlan = pbProto->MutableExtension(OstProto::svlan); + + svlan->set_tpid(0x88a8); + svlan->set_is_override_tpid(true); + + // If a eth2 protocol precedes svlan, we remove the eth2 protocol + // 'coz the eth2.etherType is actually the svlan.tpid + // + // We assume that the current protocol is the last in the stream + int index = stream->protocol_size() - 1; + if ((index > 1) + && (stream->protocol(index).protocol_id().id() + == OstProto::Protocol::kSvlanFieldNumber) + && (stream->protocol(index - 1).protocol_id().id() + == OstProto::Protocol::kEth2FieldNumber)) + { + stream->mutable_protocol()->SwapElements(index, index - 1); + Q_ASSERT(stream->protocol(index).protocol_id().id() + == OstProto::Protocol::kEth2FieldNumber); + stream->mutable_protocol()->RemoveLast(); + } +} + +void PdmlSvlanProtocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream) +{ + if ((name == "ieee8021ad.id") || (name == "ieee8021ad.svid")) + { + bool isOk; + OstProto::Vlan *svlan = pbProto->MutableExtension(OstProto::svlan); + uint tag = attributes.value("unmaskedvalue").isEmpty() ? + attributes.value("value").toString().toUInt(&isOk, kBaseHex) : + attributes.value("unmaskedvalue").toString().toUInt(&isOk,kBaseHex); + + svlan->set_vlan_tag(tag); + } + else if (name == "ieee8021ad.cvid") + { + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kSvlanFieldNumber); + + OstProto::Vlan *svlan = proto->MutableExtension(OstProto::svlan); + + svlan->set_tpid(0x88a8); + svlan->set_is_override_tpid(true); + + bool isOk; + uint tag = attributes.value("unmaskedvalue").isEmpty() ? + attributes.value("value").toString().toUInt(&isOk, kBaseHex) : + attributes.value("unmaskedvalue").toString().toUInt(&isOk,kBaseHex); + + svlan->set_vlan_tag(tag); + } + else if (name == "ieee8021ah.etype") // yes 'ah' not 'ad' - not a typo! + { + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kEth2FieldNumber); + + bool isOk; + OstProto::Eth2 *eth2 = proto->MutableExtension(OstProto::eth2); + + eth2->set_type(attributes.value("value") + .toString().toUInt(&isOk, kBaseHex)); + eth2->set_is_override_type(true); + } +} + diff --git a/common/svlanpdml.h b/common/svlanpdml.h new file mode 100644 index 0000000..517cc0f --- /dev/null +++ b/common/svlanpdml.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SVLAN_PDML_H +#define _SVLAN_PDML_H + +#include "pdmlprotocol.h" + +class PdmlSvlanProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); +protected: + PdmlSvlanProtocol(); +}; + +#endif diff --git a/common/tcp.cpp b/common/tcp.cpp new file mode 100644 index 0000000..2f9a5b4 --- /dev/null +++ b/common/tcp.cpp @@ -0,0 +1,606 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "tcp.h" + + +TcpProtocol::TcpProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +TcpProtocol::~TcpProtocol() +{ +} + +AbstractProtocol* TcpProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new TcpProtocol(stream, parent); +} + +quint32 TcpProtocol::protocolNumber() const +{ + return OstProto::Protocol::kTcpFieldNumber; +} + +void TcpProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::tcp)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void TcpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::tcp)) + data.MergeFrom(protocol.GetExtension(OstProto::tcp)); +} + +QString TcpProtocol::name() const +{ + return QString("Transmission Control Protocol"); +} + +QString TcpProtocol::shortName() const +{ + return QString("TCP"); +} + +AbstractProtocol::ProtocolIdType TcpProtocol::protocolIdType() const +{ + return ProtocolIdTcpUdp; +} + +quint32 TcpProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdIp: return 0x06; + default: break; + } + + return AbstractProtocol::protocolId(type); +} + +int TcpProtocol::fieldCount() const +{ + return tcp_fieldCount; +} + +AbstractProtocol::FieldFlags TcpProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case tcp_src_port: + case tcp_dst_port: + case tcp_seq_num: + case tcp_ack_num: + case tcp_hdrlen: + case tcp_rsvd: + case tcp_flags: + case tcp_window: + break; + + case tcp_cksum: + flags |= CksumField; + break; + + case tcp_urg_ptr: + break; + + case tcp_is_override_src_port: + case tcp_is_override_dst_port: + case tcp_is_override_hdrlen: + case tcp_is_override_cksum: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +QVariant TcpProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case tcp_src_port: + { + quint16 srcPort; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_src_port()) + srcPort = data.src_port(); + else + srcPort = payloadProtocolId(ProtocolIdTcpUdp); + break; + default: + srcPort = 0; // avoid the 'maybe used unitialized' warning + break; + } + switch(attrib) + { + case FieldName: + return QString("Source Port"); + case FieldValue: + return srcPort; + case FieldTextValue: + return QString("%1").arg(srcPort); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(srcPort, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case tcp_dst_port: + { + quint16 dstPort; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_dst_port()) + dstPort = data.dst_port(); + else + dstPort = payloadProtocolId(ProtocolIdTcpUdp); + break; + default: + dstPort = 0; // avoid the 'maybe used unitialized' warning + break; + } + switch(attrib) + { + case FieldName: + return QString("Destination Port"); + case FieldValue: + return dstPort; + case FieldTextValue: + return QString("%1").arg(dstPort); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(dstPort, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case tcp_seq_num: + switch(attrib) + { + case FieldName: + return QString("Sequence Number"); + case FieldValue: + return data.seq_num(); + case FieldTextValue: + return QString("%1").arg(data.seq_num()); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian((quint32) data.seq_num(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + + case tcp_ack_num: + switch(attrib) + { + case FieldName: + return QString("Acknowledgement Number"); + case FieldValue: + return data.ack_num(); + case FieldTextValue: + return QString("%1").arg(data.ack_num()); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(4); + qToBigEndian((quint32) data.ack_num(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + + case tcp_hdrlen: + switch(attrib) + { + case FieldName: + return QString("Header Length"); + case FieldValue: + if (data.is_override_hdrlen()) + return ((data.hdrlen_rsvd() >> 4) & 0x0F); + else + return 5; + case FieldTextValue: + if (data.is_override_hdrlen()) + return QString("%1 bytes").arg( + 4 * ((data.hdrlen_rsvd() >> 4) & 0x0F)); + else + return QString("20 bytes"); + case FieldFrameValue: + if (data.is_override_hdrlen()) + return QByteArray(1, + (char)((data.hdrlen_rsvd() >> 4) & 0x0F)); + else + return QByteArray(1, (char) 0x05); + case FieldBitSize: + return 4; + default: + break; + } + break; + + case tcp_rsvd: + switch(attrib) + { + case FieldName: + return QString("Reserved"); + case FieldValue: + return (data.hdrlen_rsvd() & 0x0F); + case FieldTextValue: + return QString("%1").arg(data.hdrlen_rsvd() & 0x0F); + case FieldFrameValue: + return QByteArray(1, (char)(data.hdrlen_rsvd() & 0x0F)); + case FieldBitSize: + return 4; + default: + break; + } + break; + + case tcp_flags: + switch(attrib) + { + case FieldName: + return QString("Flags"); + case FieldValue: + return (data.flags()); + case FieldTextValue: + { + QString s; + s.append("URG: "); + s.append(data.flags() & TCP_FLAG_URG ? "1" : "0"); + s.append(" ACK: "); + s.append(data.flags() & TCP_FLAG_ACK ? "1" : "0"); + s.append(" PSH: "); + s.append(data.flags() & TCP_FLAG_PSH ? "1" : "0"); + s.append(" RST: "); + s.append(data.flags() & TCP_FLAG_RST ? "1" : "0"); + s.append(" SYN: "); + s.append(data.flags() & TCP_FLAG_SYN ? "1" : "0"); + s.append(" FIN: "); + s.append(data.flags() & TCP_FLAG_FIN ? "1" : "0"); + return s; + } + case FieldFrameValue: + return QByteArray(1, (char)(data.flags() & 0x3F)); + default: + break; + } + break; + + case tcp_window: + switch(attrib) + { + case FieldName: + return QString("Window Size"); + case FieldValue: + return data.window(); + case FieldTextValue: + return QString("%1").arg(data.window()); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.window(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + + case tcp_cksum: + switch(attrib) + { + case FieldName: + return QString("Checksum"); + case FieldValue: + { + quint16 cksum; + + if (data.is_override_cksum()) + cksum = data.cksum(); + else + cksum = protocolFrameCksum(streamIndex, CksumTcpUdp); + + return cksum; + } + case FieldTextValue: + { + quint16 cksum; + + if (data.is_override_cksum()) + cksum = data.cksum(); + else + cksum = protocolFrameCksum(streamIndex, CksumTcpUdp); + + return QString("0x%1").arg(cksum, 4, BASE_HEX, QChar('0')); + } + case FieldFrameValue: + { + quint16 cksum; + + if (data.is_override_cksum()) + cksum = data.cksum(); + else + cksum = protocolFrameCksum(streamIndex, CksumTcpUdp); + + QByteArray fv; + fv.resize(2); + qToBigEndian(cksum, (uchar*) fv.data()); + return fv; + } + case FieldBitSize: + return 16; + default: + break; + } + break; + + case tcp_urg_ptr: + switch(attrib) + { + case FieldName: + return QString("Urgent Pointer"); + case FieldValue: + return data.urg_ptr(); + case FieldTextValue: + return QString("%1").arg(data.urg_ptr()); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) data.urg_ptr(), (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + + // Meta fields + case tcp_is_override_src_port: + { + switch(attrib) + { + case FieldValue: + return data.is_override_src_port(); + default: + break; + } + break; + } + case tcp_is_override_dst_port: + { + switch(attrib) + { + case FieldValue: + return data.is_override_dst_port(); + default: + break; + } + break; + } + case tcp_is_override_hdrlen: + { + switch(attrib) + { + case FieldValue: + return data.is_override_hdrlen(); + default: + break; + } + break; + } + case tcp_is_override_cksum: + { + switch(attrib) + { + case FieldValue: + return data.is_override_cksum(); + default: + break; + } + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool TcpProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case tcp_src_port: + { + uint srcPort = value.toUInt(&isOk); + if (isOk) + data.set_src_port(srcPort); + break; + } + case tcp_dst_port: + { + uint dstPort = value.toUInt(&isOk); + if (isOk) + data.set_dst_port(dstPort); + break; + } + case tcp_seq_num: + { + uint seqNum = value.toUInt(&isOk); + if (isOk) + data.set_seq_num(seqNum); + break; + } + case tcp_ack_num: + { + uint ackNum = value.toUInt(&isOk); + if (isOk) + data.set_ack_num(ackNum); + break; + } + case tcp_hdrlen: + { + uint hdrLen = value.toUInt(&isOk); + if (isOk) + data.set_hdrlen_rsvd( + (data.hdrlen_rsvd() & 0x0F) | (hdrLen << 4)); + break; + } + case tcp_rsvd: + { + uint rsvd = value.toUInt(&isOk); + if (isOk) + data.set_hdrlen_rsvd( + (data.hdrlen_rsvd() & 0xF0) | (rsvd & 0x0F)); + break; + } + case tcp_flags: + { + uint flags = value.toUInt(&isOk); + if (isOk) + data.set_flags(flags); + break; + } + case tcp_window: + { + uint window = value.toUInt(&isOk); + if (isOk) + data.set_window(window); + break; + } + case tcp_cksum: + { + uint cksum = value.toUInt(&isOk); + if (isOk) + data.set_cksum(cksum); + break; + } + case tcp_urg_ptr: + { + uint urgPtr = value.toUInt(&isOk); + if (isOk) + data.set_urg_ptr(urgPtr); + break; + } + case tcp_is_override_src_port: + { + data.set_is_override_src_port(value.toBool()); + isOk = true; + break; + } + case tcp_is_override_dst_port: + { + data.set_is_override_dst_port(value.toBool()); + isOk = true; + break; + } + case tcp_is_override_hdrlen: + { + data.set_is_override_hdrlen(value.toBool()); + isOk = true; + break; + } + case tcp_is_override_cksum: + { + data.set_is_override_cksum(value.toBool()); + isOk = true; + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +bool TcpProtocol::isProtocolFrameValueVariable() const +{ + if (data.is_override_cksum()) + return false; + else + return isProtocolFramePayloadValueVariable(); +} + +int TcpProtocol::protocolFrameVariableCount() const +{ + if (data.is_override_cksum()) + return 1; + + return protocolFramePayloadVariableCount(); +} + diff --git a/common/tcp.h b/common/tcp.h new file mode 100644 index 0000000..276ba65 --- /dev/null +++ b/common/tcp.h @@ -0,0 +1,89 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _TCP_H +#define _TCP_H + +#include "abstractprotocol.h" + +#include "tcp.pb.h" + +#define TCP_FLAG_URG 0x20 +#define TCP_FLAG_ACK 0x10 +#define TCP_FLAG_PSH 0x08 +#define TCP_FLAG_RST 0x04 +#define TCP_FLAG_SYN 0x02 +#define TCP_FLAG_FIN 0x01 + +class TcpProtocol : public AbstractProtocol +{ +public: + enum tcpfield + { + tcp_src_port = 0, + tcp_dst_port, + tcp_seq_num, + tcp_ack_num, + tcp_hdrlen, + tcp_rsvd, + tcp_flags, + tcp_window, + tcp_cksum, + tcp_urg_ptr, + + tcp_is_override_src_port, + tcp_is_override_dst_port, + tcp_is_override_hdrlen, + tcp_is_override_cksum, + + tcp_fieldCount + }; + + TcpProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~TcpProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual ProtocolIdType protocolIdType() const; + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual bool isProtocolFrameValueVariable() const; + virtual int protocolFrameVariableCount() const; + +private: + OstProto::Tcp data; +}; + +#endif diff --git a/common/tcp.proto b/common/tcp.proto new file mode 100644 index 0000000..93bd762 --- /dev/null +++ b/common/tcp.proto @@ -0,0 +1,47 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; +// Tcp +message Tcp { + optional bool is_override_src_port = 1; + optional bool is_override_dst_port = 2; + optional bool is_override_hdrlen = 3; + optional bool is_override_cksum = 4; + + optional uint32 src_port = 5 [default = 49152]; + optional uint32 dst_port = 6 [default = 49153]; + + optional uint32 seq_num = 7 [default = 129018]; + optional uint32 ack_num = 8; + + optional uint32 hdrlen_rsvd = 9 [default = 0x50]; + optional uint32 flags = 10; + + optional uint32 window = 11 [default = 1024]; + optional uint32 cksum = 12; + optional uint32 urg_ptr = 13; +} + +extend Protocol { + optional Tcp tcp = 400; +} + diff --git a/common/tcp.ui b/common/tcp.ui new file mode 100644 index 0000000..6f3eee8 --- /dev/null +++ b/common/tcp.ui @@ -0,0 +1,268 @@ + + tcp + + + + 0 + 0 + 447 + 194 + + + + Form + + + + + + Override Source Port + + + + + + + false + + + + + + + Qt::Vertical + + + + + + + Override Checksum + + + + + + + false + + + >HH HH; + + + + + + + Override Destination Port + + + + + + + false + + + + + + + Urgent Pointer + + + + + + + + + + Sequence Number + + + + + + + + + + Flags + + + + + + URG + + + + + + + ACK + + + + + + + PSH + + + + + + + RST + + + + + + + SYN + + + + + + + FIN + + + + + + + + + + Qt::Horizontal + + + + 21 + 20 + + + + + + + + Acknowledgement Number + + + + + + + + + + Override Header Length (x4) + + + + + + + false + + + + + + + Window + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + cbTcpHdrLenOverride + toggled(bool) + leTcpHdrLen + setEnabled(bool) + + + 141 + 123 + + + 187 + 123 + + + + + cbTcpCksumOverride + toggled(bool) + leTcpCksum + setEnabled(bool) + + + 316 + 14 + + + 384 + 17 + + + + + cbTcpSrcPortOverride + toggled(bool) + leTcpSrcPort + setEnabled(bool) + + + 159 + 16 + + + 178 + 18 + + + + + cbTcpDstPortOverride + toggled(bool) + leTcpDstPort + setEnabled(bool) + + + 147 + 45 + + + 180 + 44 + + + + + diff --git a/common/tcpconfig.cpp b/common/tcpconfig.cpp new file mode 100644 index 0000000..ff08f7d --- /dev/null +++ b/common/tcpconfig.cpp @@ -0,0 +1,175 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "tcpconfig.h" +#include "tcp.h" + +TcpConfigForm::TcpConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); +} + +TcpConfigForm::~TcpConfigForm() +{ +} + +TcpConfigForm* TcpConfigForm::createInstance() +{ + return new TcpConfigForm; +} + +void TcpConfigForm::loadWidget(AbstractProtocol *proto) +{ + leTcpSrcPort->setText( + proto->fieldData( + TcpProtocol::tcp_src_port, + AbstractProtocol::FieldValue + ).toString()); + cbTcpSrcPortOverride->setChecked( + proto->fieldData( + TcpProtocol::tcp_is_override_src_port, + AbstractProtocol::FieldValue + ).toBool()); + + leTcpDstPort->setText( + proto->fieldData( + TcpProtocol::tcp_dst_port, + AbstractProtocol::FieldValue + ).toString()); + cbTcpDstPortOverride->setChecked( + proto->fieldData( + TcpProtocol::tcp_is_override_dst_port, + AbstractProtocol::FieldValue + ).toBool()); + + leTcpSeqNum->setText( + proto->fieldData( + TcpProtocol::tcp_seq_num, + AbstractProtocol::FieldValue + ).toString()); + leTcpAckNum->setText( + proto->fieldData( + TcpProtocol::tcp_ack_num, + AbstractProtocol::FieldValue + ).toString()); + + leTcpHdrLen->setText( + proto->fieldData( + TcpProtocol::tcp_hdrlen, + AbstractProtocol::FieldValue + ).toString()); + cbTcpHdrLenOverride->setChecked( + proto->fieldData( + TcpProtocol::tcp_is_override_hdrlen, + AbstractProtocol::FieldValue + ).toBool()); + + leTcpWindow->setText( + proto->fieldData( + TcpProtocol::tcp_window, + AbstractProtocol::FieldValue + ).toString()); + + leTcpCksum->setText(uintToHexStr( + proto->fieldData( + TcpProtocol::tcp_cksum, + AbstractProtocol::FieldValue + ).toUInt(), 2)); + cbTcpCksumOverride->setChecked( + proto->fieldData( + TcpProtocol::tcp_is_override_cksum, + AbstractProtocol::FieldValue + ).toBool()); + + leTcpUrgentPointer->setText( + proto->fieldData( + TcpProtocol::tcp_urg_ptr, + AbstractProtocol::FieldValue + ).toString()); + + uint flags = proto->fieldData( + TcpProtocol::tcp_flags, + AbstractProtocol::FieldValue + ).toUInt(); + + cbTcpFlagsUrg->setChecked((flags & TCP_FLAG_URG) > 0); + cbTcpFlagsAck->setChecked((flags & TCP_FLAG_ACK) > 0); + cbTcpFlagsPsh->setChecked((flags & TCP_FLAG_PSH) > 0); + cbTcpFlagsRst->setChecked((flags & TCP_FLAG_RST) > 0); + cbTcpFlagsSyn->setChecked((flags & TCP_FLAG_SYN) > 0); + cbTcpFlagsFin->setChecked((flags & TCP_FLAG_FIN) > 0); +} + +void TcpConfigForm::storeWidget(AbstractProtocol *proto) +{ + int ff = 0; + + proto->setFieldData( + TcpProtocol::tcp_src_port, + leTcpSrcPort->text()); + proto->setFieldData( + TcpProtocol::tcp_is_override_src_port, + cbTcpSrcPortOverride->isChecked()); + proto->setFieldData( + TcpProtocol::tcp_dst_port, + leTcpDstPort->text()); + proto->setFieldData( + TcpProtocol::tcp_is_override_dst_port, + cbTcpDstPortOverride->isChecked()); + + proto->setFieldData( + TcpProtocol::tcp_seq_num, + leTcpSeqNum->text()); + proto->setFieldData( + TcpProtocol::tcp_ack_num, + leTcpAckNum->text()); + + proto->setFieldData( + TcpProtocol::tcp_hdrlen, + leTcpHdrLen->text()); + proto->setFieldData( + TcpProtocol::tcp_is_override_hdrlen, + cbTcpHdrLenOverride->isChecked()); + + proto->setFieldData( + TcpProtocol::tcp_window, + leTcpWindow->text()); + + proto->setFieldData( + TcpProtocol::tcp_cksum, + hexStrToUInt(leTcpCksum->text())); + proto->setFieldData( + TcpProtocol::tcp_is_override_cksum, + cbTcpCksumOverride->isChecked()); + + proto->setFieldData( + TcpProtocol::tcp_urg_ptr, + leTcpUrgentPointer->text()); + + if (cbTcpFlagsUrg->isChecked()) ff |= TCP_FLAG_URG; + if (cbTcpFlagsAck->isChecked()) ff |= TCP_FLAG_ACK; + if (cbTcpFlagsPsh->isChecked()) ff |= TCP_FLAG_PSH; + if (cbTcpFlagsRst->isChecked()) ff |= TCP_FLAG_RST; + if (cbTcpFlagsSyn->isChecked()) ff |= TCP_FLAG_SYN; + if (cbTcpFlagsFin->isChecked()) ff |= TCP_FLAG_FIN; + + proto->setFieldData(TcpProtocol::tcp_flags, ff); +} + diff --git a/common/tcpconfig.h b/common/tcpconfig.h new file mode 100644 index 0000000..859238a --- /dev/null +++ b/common/tcpconfig.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _TCP_CONFIG_H +#define _TCP_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_tcp.h" + +class TcpConfigForm : + public AbstractProtocolConfigForm, + private Ui::tcp +{ + Q_OBJECT +public: + TcpConfigForm(QWidget *parent = 0); + virtual ~TcpConfigForm(); + + static TcpConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +}; + +#endif diff --git a/common/tcppdml.cpp b/common/tcppdml.cpp new file mode 100644 index 0000000..3980b6a --- /dev/null +++ b/common/tcppdml.cpp @@ -0,0 +1,98 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "tcppdml.h" + +#include "hexdump.pb.h" +#include "tcp.pb.h" + +PdmlTcpProtocol::PdmlTcpProtocol() +{ + ostProtoId_ = OstProto::Protocol::kTcpFieldNumber; + + fieldMap_.insert("tcp.srcport", OstProto::Tcp::kSrcPortFieldNumber); + fieldMap_.insert("tcp.dstport", OstProto::Tcp::kDstPortFieldNumber); + fieldMap_.insert("tcp.seq", OstProto::Tcp::kSeqNumFieldNumber); + fieldMap_.insert("tcp.ack", OstProto::Tcp::kAckNumFieldNumber); + fieldMap_.insert("tcp.hdr_len", OstProto::Tcp::kHdrlenRsvdFieldNumber); + fieldMap_.insert("tcp.flags", OstProto::Tcp::kFlagsFieldNumber); + fieldMap_.insert("tcp.window_size", OstProto::Tcp::kWindowFieldNumber); + fieldMap_.insert("tcp.checksum", OstProto::Tcp::kCksumFieldNumber); + fieldMap_.insert("tcp.urgent_pointer", OstProto::Tcp::kUrgPtrFieldNumber); +} + +PdmlProtocol* PdmlTcpProtocol::createInstance() +{ + return new PdmlTcpProtocol(); +} + +void PdmlTcpProtocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream* /*stream*/) +{ + if (name == "tcp.options") + options_ = QByteArray::fromHex(attributes.value("value").toString().toUtf8()); + else if (name == "") + { + if (attributes.value("show").toString().startsWith("Acknowledgement number")) + { + bool isOk; + OstProto::Tcp *tcp = pbProto->MutableExtension(OstProto::tcp); + + tcp->set_ack_num(attributes.value("value").toString().toUInt(&isOk, kBaseHex)); + } +#if 0 + else if (attributes.value("show").toString().startsWith("TCP segment data")) + { + segmentData_ = QByteArray::fromHex(attributes.value("value").toString().toUtf8()); + stream->mutable_core()->mutable_name()->insert(0, + segmentData_.constData(), segmentData_.size()); + } +#endif + } +} + +void PdmlTcpProtocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream) +{ + OstProto::Tcp *tcp = pbProto->MutableExtension(OstProto::tcp); + + qDebug("Tcp: post\n"); + + tcp->set_is_override_src_port(true); + tcp->set_is_override_dst_port(true); + tcp->set_is_override_hdrlen(true); + tcp->set_is_override_cksum(true); + + if (options_.size()) + { + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kHexDumpFieldNumber); + + OstProto::HexDump *hexDump = proto->MutableExtension(OstProto::hexDump); + + hexDump->mutable_content()->append(options_.constData(), + options_.size()); + hexDump->set_pad_until_end(false); + options_.resize(0); + } +} + diff --git a/common/tcppdml.h b/common/tcppdml.h new file mode 100644 index 0000000..5c3d1c6 --- /dev/null +++ b/common/tcppdml.h @@ -0,0 +1,42 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _TCP_PDML_H +#define _TCP_PDML_H + +#include "pdmlprotocol.h" + +class PdmlTcpProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); +protected: + PdmlTcpProtocol(); +private: + QByteArray options_; + QByteArray segmentData_; +}; + +#endif diff --git a/common/textproto.cpp b/common/textproto.cpp new file mode 100644 index 0000000..285668d --- /dev/null +++ b/common/textproto.cpp @@ -0,0 +1,240 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "textproto.h" + +TextProtocol::TextProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +TextProtocol::~TextProtocol() +{ +} + +AbstractProtocol* TextProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new TextProtocol(stream, parent); +} + +quint32 TextProtocol::protocolNumber() const +{ + return OstProto::Protocol::kTextProtocolFieldNumber; +} + +void TextProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::textProtocol)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void TextProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::textProtocol)) + data.MergeFrom(protocol.GetExtension(OstProto::textProtocol)); +} + +QString TextProtocol::name() const +{ + return QString("Text Protocol"); +} + +QString TextProtocol::shortName() const +{ + return QString("TEXT"); +} + +quint32 TextProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdTcpUdp: return data.port_num(); + default:break; + } + + return AbstractProtocol::protocolId(type); +} + +int TextProtocol::fieldCount() const +{ + return textProto_fieldCount; +} + +AbstractProtocol::FieldFlags TextProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case textProto_text: + break; + + case textProto_portNum: + case textProto_eol: + case textProto_encoding: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +QVariant TextProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case textProto_text: + { + switch(attrib) + { + case FieldName: + return QString("Text"); + case FieldValue: + case FieldTextValue: + return QString().fromStdString(data.text()); + case FieldFrameValue: + { + QString text; + Q_ASSERT(data.encoding() == OstProto::TextProtocol::kUtf8); + text = QString().fromStdString(data.text()); + + if (data.eol() == OstProto::TextProtocol::kCrLf) + text.replace('\n', "\r\n"); + else if (data.eol() == OstProto::TextProtocol::kCr) + text.replace('\n', '\r'); + + return text.toUtf8(); + } + default: + break; + } + break; + + } + + // Meta fields + case textProto_portNum: + { + switch(attrib) + { + case FieldValue: + return data.port_num(); + default: + break; + } + break; + } + case textProto_eol: + { + switch(attrib) + { + case FieldValue: + return data.eol(); + default: + break; + } + break; + } + case textProto_encoding: + { + switch(attrib) + { + case FieldValue: + return data.encoding(); + default: + break; + } + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool TextProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case textProto_text: + { + data.set_text(value.toString().toUtf8()); + isOk = true; + break; + } + case textProto_portNum: + { + uint portNum = value.toUInt(&isOk); + if (isOk) + data.set_port_num(portNum); + break; + } + case textProto_eol: + { + uint eol = value.toUInt(&isOk); + if (isOk && data.EndOfLine_IsValid(eol)) + data.set_eol((OstProto::TextProtocol::EndOfLine) eol); + else + isOk = false; + break; + } + case textProto_encoding: + { + uint enc = value.toUInt(&isOk); + if (isOk && data.TextEncoding_IsValid(enc)) + data.set_encoding((OstProto::TextProtocol::TextEncoding) enc); + else + isOk = false; + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +int TextProtocol::protocolFrameSize(int streamIndex) const +{ + return fieldData(textProto_text, FieldFrameValue, streamIndex) + .toByteArray().size() ; +} diff --git a/common/textproto.h b/common/textproto.h new file mode 100644 index 0000000..8c00e47 --- /dev/null +++ b/common/textproto.h @@ -0,0 +1,77 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _TEXT_PROTOCOL_H +#define _TEXT_PROTOCOL_H + +#include "abstractprotocol.h" +#include "textproto.pb.h" + +/* +TextProtocol Protocol Frame Format - + specified text with the specified line ending and encoded with the + specified encoding +*/ + +class TextProtocol : public AbstractProtocol +{ +public: + enum textProtocolField + { + // Frame Fields + textProto_text = 0, + + // Meta Fields + textProto_portNum, + textProto_eol, + textProto_encoding, + + textProto_fieldCount + }; + + TextProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~TextProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual int protocolFrameSize(int streamIndex = 0) const; + +private: + OstProto::TextProtocol data; +}; + +#endif diff --git a/common/textproto.proto b/common/textproto.proto new file mode 100644 index 0000000..e20e496 --- /dev/null +++ b/common/textproto.proto @@ -0,0 +1,44 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Any Text based protocol +message TextProtocol { + enum TextEncoding { + kUtf8 = 0; + } + + enum EndOfLine { + kCr = 0; + kLf = 1; + kCrLf = 2; + } + + optional uint32 port_num = 1 [default = 80]; + optional TextEncoding encoding = 2 [default = kUtf8]; + optional string text = 3; + optional EndOfLine eol = 4 [default = kLf]; +} + +extend Protocol { + optional TextProtocol textProtocol = 500; +} diff --git a/common/textproto.ui b/common/textproto.ui new file mode 100644 index 0000000..6e7ebdb --- /dev/null +++ b/common/textproto.ui @@ -0,0 +1,108 @@ + + TextProto + + + + 0 + 0 + 535 + 300 + + + + Form + + + + + + TCP/UDP Port Number (Protocol) + + + portNumCombo + + + + + + + + 2 + 0 + + + + + + + + Line Ending + + + + + + + 2 + + + + CR + + + + + LF + + + + + CRLF + + + + + + + + Encode as + + + encodingCombo + + + + + + + + 1 + 0 + + + + + UTF-8 + + + + + + + + false + + + + + + + + IntComboBox + QComboBox +
intcombobox.h
+
+
+ + +
diff --git a/common/textprotoconfig.cpp b/common/textprotoconfig.cpp new file mode 100644 index 0000000..0242ffa --- /dev/null +++ b/common/textprotoconfig.cpp @@ -0,0 +1,84 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "textprotoconfig.h" +#include "textproto.h" + +TextProtocolConfigForm::TextProtocolConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); + + portNumCombo->setValidator(new QIntValidator(0, 0xFFFF, this)); + portNumCombo->addItem(0, "Reserved"); + portNumCombo->addItem(80, "HTTP"); + portNumCombo->addItem(554, "RTSP"); + portNumCombo->addItem(5060, "SIP"); +} + +TextProtocolConfigForm::~TextProtocolConfigForm() +{ +} + +TextProtocolConfigForm* TextProtocolConfigForm::createInstance() +{ + return new TextProtocolConfigForm; +} + +void TextProtocolConfigForm::loadWidget(AbstractProtocol *proto) +{ + portNumCombo->setValue( + proto->fieldData( + TextProtocol::textProto_portNum, + AbstractProtocol::FieldValue + ).toUInt()); + eolCombo->setCurrentIndex( + proto->fieldData( + TextProtocol::textProto_eol, + AbstractProtocol::FieldValue + ).toUInt()); + encodingCombo->setCurrentIndex( + proto->fieldData( + TextProtocol::textProto_encoding, + AbstractProtocol::FieldValue + ).toUInt()); + protoText->setText( + proto->fieldData( + TextProtocol::textProto_text, + AbstractProtocol::FieldValue + ).toString()); +} + +void TextProtocolConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + TextProtocol::textProto_portNum, + portNumCombo->currentValue()); + proto->setFieldData( + TextProtocol::textProto_eol, + eolCombo->currentIndex()); + proto->setFieldData( + TextProtocol::textProto_encoding, + encodingCombo->currentIndex()); + + proto->setFieldData( + TextProtocol::textProto_text, + protoText->toPlainText()); +} + diff --git a/common/textprotoconfig.h b/common/textprotoconfig.h new file mode 100644 index 0000000..a1971ab --- /dev/null +++ b/common/textprotoconfig.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _TEXT_PROTOCOL_CONFIG_H +#define _TEXT_PROTOCOL_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_textproto.h" + +class TextProtocolConfigForm : + public AbstractProtocolConfigForm, + private Ui::TextProto +{ + Q_OBJECT +public: + TextProtocolConfigForm(QWidget *parent = 0); + virtual ~TextProtocolConfigForm(); + + static TextProtocolConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +}; + +#endif diff --git a/common/textprotopdml.cpp b/common/textprotopdml.cpp new file mode 100644 index 0000000..26ccfe7 --- /dev/null +++ b/common/textprotopdml.cpp @@ -0,0 +1,171 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "textprotopdml.h" + +#include "textproto.pb.h" + +PdmlTextProtocol::PdmlTextProtocol() +{ + ostProtoId_ = OstProto::Protocol::kTextProtocolFieldNumber; +} + +PdmlProtocol* PdmlTextProtocol::createInstance() +{ + return new PdmlTextProtocol(); +} + +void PdmlTextProtocol::preProtocolHandler(QString /*name*/, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream) +{ + bool isOk; + int size; + int pos = attributes.value("pos").toString().toUInt(&isOk); + + if (!isOk) + { + if (expectedPos >= 0) + expPos_ = pos = expectedPos; + else + goto _skip_pos_size_proc; + } + + size = attributes.value("size").toString().toUInt(&isOk); + if (!isOk) + goto _skip_pos_size_proc; + + // If pos+size goes beyond the frame length, this is a "reassembled" + // protocol and should be skipped + if ((pos + size) > int(stream->core().frame_len())) + goto _skip_pos_size_proc; + + expPos_ = pos; + endPos_ = expPos_ + size; + +_skip_pos_size_proc: + qDebug("expPos_ = %d, endPos_ = %d", expPos_, endPos_); + OstProto::TextProtocol *text = pbProto->MutableExtension( + OstProto::textProtocol); + + text->set_port_num(0); + text->set_eol(OstProto::TextProtocol::kCrLf); // by default we assume CRLF + + detectEol_ = true; + contentType_ = kUnknownContent; +} + +void PdmlTextProtocol::unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, OstProto::Protocol *pbProto, + OstProto::Stream* /*stream*/) +{ +_retry: + switch(contentType_) + { + case kUnknownContent: + if (name == "data") + contentType_ = kOtherContent; + else + contentType_ = kTextContent; + goto _retry; + break; + + case kTextContent: + { + OstProto::TextProtocol *text = pbProto->MutableExtension( + OstProto::textProtocol); + + if ((name == "data") + || (attributes.value("show") == "HTTP chunked response")) + { + contentType_ = kOtherContent; + goto _retry; + } + + if (pos < expPos_) + break; + + if ((pos + size) > endPos_) + break; + + if (pos > expPos_) + { + int gap = pos - expPos_; + QByteArray filler(gap, '\n'); + + if (text->eol() == OstProto::TextProtocol::kCrLf) + { + if (gap & 0x01) // Odd + { + filler.resize(gap/2 + 1); + filler[0]=int(' '); + } + else // Even + filler.resize(gap/2); + } + + text->mutable_text()->append(filler.constData(), filler.size()); + expPos_ += gap; + } + + QByteArray line = QByteArray::fromHex( + attributes.value("value").toString().toUtf8()); + + if (detectEol_) + { + if (line.right(2) == "\r\n") + text->set_eol(OstProto::TextProtocol::kCrLf); + else if (line.right(1) == "\r") + text->set_eol(OstProto::TextProtocol::kCr); + else if (line.right(1) == "\n") + text->set_eol(OstProto::TextProtocol::kLf); + + detectEol_ = false; + } + + // Convert line endings to LF only - Qt reqmt that TextProto honours + line.replace("\r\n", "\n"); + line.replace('\r', '\n'); + + text->mutable_text()->append(line.constData(), line.size()); + expPos_ += size; + break; + } + case kOtherContent: + // Do nothing! + break; + default: + Q_ASSERT(false); + } +} + +void PdmlTextProtocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream) +{ + OstProto::TextProtocol *text = pbProto->MutableExtension( + OstProto::textProtocol); + + // Empty Text Content - remove ourselves + if (text->text().length() == 0) + stream->mutable_protocol()->RemoveLast(); + + expPos_ = endPos_ = -1; + detectEol_ = true; + contentType_ = kUnknownContent; +} diff --git a/common/textprotopdml.h b/common/textprotopdml.h new file mode 100644 index 0000000..daaca0c --- /dev/null +++ b/common/textprotopdml.h @@ -0,0 +1,53 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _TEXT_PROTO_PDML_H +#define _TEXT_PROTO_PDML_H + +#include "pdmlprotocol.h" + +class PdmlTextProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); +protected: + PdmlTextProtocol(); +private: + enum ContentType { + kUnknownContent, + kTextContent, + kOtherContent + }; + + bool detectEol_; + ContentType contentType_; + int expPos_; + int endPos_; +}; + +#endif diff --git a/common/udp.cpp b/common/udp.cpp new file mode 100644 index 0000000..f3d051c --- /dev/null +++ b/common/udp.cpp @@ -0,0 +1,431 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "udp.h" + +UdpProtocol::UdpProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +UdpProtocol::~UdpProtocol() +{ +} + +AbstractProtocol* UdpProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new UdpProtocol(stream, parent); +} + +quint32 UdpProtocol::protocolNumber() const +{ + return OstProto::Protocol::kUdpFieldNumber; +} + +void UdpProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::udp)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void UdpProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::udp)) + data.MergeFrom(protocol.GetExtension(OstProto::udp)); +} + +QString UdpProtocol::name() const +{ + return QString("User Datagram Protocol"); +} + +QString UdpProtocol::shortName() const +{ + return QString("UDP"); +} + +AbstractProtocol::ProtocolIdType UdpProtocol::protocolIdType() const +{ + return ProtocolIdTcpUdp; +} + +quint32 UdpProtocol::protocolId(ProtocolIdType type) const +{ + switch(type) + { + case ProtocolIdIp: return 0x11; + default: break; + } + + return AbstractProtocol::protocolId(type); +} + +int UdpProtocol::fieldCount() const +{ + return udp_fieldCount; +} + +AbstractProtocol::FieldFlags UdpProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case udp_srcPort: + case udp_dstPort: + case udp_totLen: + break; + + case udp_cksum: + flags |= CksumField; + break; + + case udp_isOverrideSrcPort: + case udp_isOverrideDstPort: + case udp_isOverrideTotLen: + case udp_isOverrideCksum: + flags &= ~FrameField; + flags |= MetaField; + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return flags; +} + +QVariant UdpProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case udp_srcPort: + { + quint16 srcPort; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_src_port()) + srcPort = data.src_port(); + else + srcPort = payloadProtocolId(ProtocolIdTcpUdp); + break; + default: + srcPort = 0; // avoid the 'maybe used unitialized' warning + break; + } + switch(attrib) + { + case FieldName: + return QString("Source Port"); + case FieldValue: + return srcPort; + case FieldTextValue: + return QString("%1").arg(srcPort); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(srcPort, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case udp_dstPort: + { + quint16 dstPort; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + if (data.is_override_dst_port()) + dstPort = data.dst_port(); + else + dstPort = payloadProtocolId(ProtocolIdTcpUdp); + break; + default: + dstPort = 0; // avoid the 'maybe used unitialized' warning + break; + } + switch(attrib) + { + case FieldName: + return QString("Destination Port"); + case FieldValue: + return dstPort; + case FieldTextValue: + return QString("%1").arg(dstPort); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(dstPort, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + case udp_totLen: + { + + switch(attrib) + { + case FieldName: + return QString("Datagram Length"); + case FieldValue: + { + int totlen; + + totlen = data.is_override_totlen() ? + data.totlen() : + (protocolFramePayloadSize(streamIndex) + 8); + return totlen; + } + case FieldFrameValue: + { + QByteArray fv; + int totlen; + totlen = data.is_override_totlen() ? + data.totlen() : + (protocolFramePayloadSize(streamIndex) + 8); + fv.resize(2); + qToBigEndian((quint16) totlen, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + { + int totlen; + totlen = data.is_override_totlen() ? + data.totlen() : + (protocolFramePayloadSize(streamIndex) + 8); + return QString("%1").arg(totlen); + } + case FieldBitSize: + return 16; + default: + break; + } + break; + } + case udp_cksum: + { + quint16 cksum; + + switch(attrib) + { + case FieldValue: + case FieldFrameValue: + case FieldTextValue: + { + if (data.is_override_cksum()) + cksum = data.cksum(); + else + cksum = protocolFrameCksum(streamIndex, CksumTcpUdp); + qDebug("UDP cksum = %hu", cksum); + break; + } + default: + cksum = 0; + break; + } + + switch(attrib) + { + case FieldName: + return QString("Checksum"); + case FieldValue: + return cksum; + case FieldFrameValue: + { + QByteArray fv; + + fv.resize(2); + qToBigEndian(cksum, (uchar*) fv.data()); + return fv; + } + case FieldTextValue: + return QString("0x%1"). + arg(cksum, 4, BASE_HEX, QChar('0'));; + case FieldBitSize: + return 16; + default: + break; + } + break; + } + + // Meta fields + case udp_isOverrideSrcPort: + { + switch(attrib) + { + case FieldValue: + return data.is_override_src_port(); + default: + break; + } + break; + } + case udp_isOverrideDstPort: + { + switch(attrib) + { + case FieldValue: + return data.is_override_dst_port(); + default: + break; + } + break; + } + case udp_isOverrideTotLen: + { + switch(attrib) + { + case FieldValue: + return data.is_override_totlen(); + default: + break; + } + break; + } + case udp_isOverrideCksum: + { + switch(attrib) + { + case FieldValue: + return data.is_override_cksum(); + default: + break; + } + break; + } + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool UdpProtocol::setFieldData(int index, const QVariant& value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case udp_isOverrideSrcPort: + { + data.set_is_override_src_port(value.toBool()); + isOk = true; + break; + } + case udp_isOverrideDstPort: + { + data.set_is_override_dst_port(value.toBool()); + isOk = true; + break; + } + case udp_isOverrideTotLen: + { + data.set_is_override_totlen(value.toBool()); + isOk = true; + break; + } + case udp_isOverrideCksum: + { + data.set_is_override_cksum(value.toBool()); + isOk = true; + break; + } + case udp_srcPort: + { + uint srcPort = value.toUInt(&isOk); + if (isOk) + data.set_src_port(srcPort); + break; + } + case udp_dstPort: + { + uint dstPort = value.toUInt(&isOk); + if (isOk) + data.set_dst_port(dstPort); + break; + } + case udp_totLen: + { + uint totLen = value.toUInt(&isOk); + if (isOk) + data.set_totlen(totLen); + break; + } + case udp_cksum: + { + uint cksum = value.toUInt(&isOk); + if (isOk) + data.set_cksum(cksum); + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +bool UdpProtocol::isProtocolFrameValueVariable() const +{ + if (data.is_override_totlen() && data.is_override_cksum()) + return false; + else + return isProtocolFramePayloadValueVariable(); +} + +int UdpProtocol::protocolFrameVariableCount() const +{ + if (data.is_override_totlen() && data.is_override_cksum()) + return 1; + + return protocolFramePayloadVariableCount(); +} diff --git a/common/udp.h b/common/udp.h new file mode 100644 index 0000000..56285b4 --- /dev/null +++ b/common/udp.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _UDP_H +#define _UDP_H + +#include "abstractprotocol.h" +#include "udp.pb.h" + +class UdpProtocol : public AbstractProtocol +{ +public: + enum udpfield + { + udp_srcPort = 0, + udp_dstPort, + udp_totLen, + udp_cksum, + + udp_isOverrideSrcPort, + udp_isOverrideDstPort, + udp_isOverrideTotLen, + udp_isOverrideCksum, + + udp_fieldCount + }; + + UdpProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~UdpProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual ProtocolIdType protocolIdType() const; + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual bool isProtocolFrameValueVariable() const; + virtual int protocolFrameVariableCount() const; + +private: + OstProto::Udp data; +}; + +#endif diff --git a/common/udp.proto b/common/udp.proto new file mode 100644 index 0000000..802135e --- /dev/null +++ b/common/udp.proto @@ -0,0 +1,39 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// UDP +message Udp { + optional bool is_override_src_port = 1; + optional bool is_override_dst_port = 2; + optional bool is_override_totlen = 3; + optional bool is_override_cksum = 4; + + optional uint32 src_port = 5 [default = 49152]; + optional uint32 dst_port = 6 [default = 49153]; + optional uint32 totlen = 7; + optional uint32 cksum = 8; +} + +extend Protocol { + optional Udp udp = 401; +} diff --git a/common/udp.ui b/common/udp.ui new file mode 100644 index 0000000..ab979e9 --- /dev/null +++ b/common/udp.ui @@ -0,0 +1,174 @@ + + udp + + + + 0 + 0 + 246 + 144 + + + + Form + + + + + + + + Override Source Port + + + + + + + false + + + + + + + Override Destination Port + + + + + + + false + + + + + + + Override Length + + + + + + + false + + + + + + + Override Checksum + + + + + + + false + + + >HH HH; + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + cbUdpLengthOverride + toggled(bool) + leUdpLength + setEnabled(bool) + + + 59 + 63 + + + 209 + 81 + + + + + cbUdpCksumOverride + toggled(bool) + leUdpCksum + setEnabled(bool) + + + 55 + 106 + + + 209 + 107 + + + + + cbUdpDstPortOverride + toggled(bool) + leUdpDstPort + setEnabled(bool) + + + 131 + 43 + + + 166 + 46 + + + + + cbUdpSrcPortOverride + toggled(bool) + leUdpSrcPort + setEnabled(bool) + + + 125 + 21 + + + 167 + 20 + + + + + diff --git a/common/udpconfig.cpp b/common/udpconfig.cpp new file mode 100644 index 0000000..dab05ec --- /dev/null +++ b/common/udpconfig.cpp @@ -0,0 +1,114 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "udpconfig.h" +#include "udp.h" + +UdpConfigForm::UdpConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); +} + +UdpConfigForm::~UdpConfigForm() +{ +} + +UdpConfigForm* UdpConfigForm::createInstance() +{ + return new UdpConfigForm; +} + + +void UdpConfigForm::loadWidget(AbstractProtocol *proto) +{ + leUdpSrcPort->setText( + proto->fieldData( + UdpProtocol::udp_srcPort, + AbstractProtocol::FieldValue + ).toString()); + cbUdpSrcPortOverride->setChecked( + proto->fieldData( + UdpProtocol::udp_isOverrideSrcPort, + AbstractProtocol::FieldValue + ).toBool()); + leUdpDstPort->setText( + proto->fieldData( + UdpProtocol::udp_dstPort, + AbstractProtocol::FieldValue + ).toString()); + cbUdpDstPortOverride->setChecked( + proto->fieldData( + UdpProtocol::udp_isOverrideDstPort, + AbstractProtocol::FieldValue + ).toBool()); + + leUdpLength->setText( + proto->fieldData( + UdpProtocol::udp_totLen, + AbstractProtocol::FieldValue + ).toString()); + cbUdpLengthOverride->setChecked( + proto->fieldData( + UdpProtocol::udp_isOverrideTotLen, + AbstractProtocol::FieldValue + ).toBool()); + + leUdpCksum->setText(uintToHexStr( + proto->fieldData( + UdpProtocol::udp_cksum, + AbstractProtocol::FieldValue + ).toUInt(), 2)); + cbUdpCksumOverride->setChecked( + proto->fieldData( + UdpProtocol::udp_isOverrideCksum, + AbstractProtocol::FieldValue + ).toBool()); +} + +void UdpConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + UdpProtocol::udp_srcPort, + leUdpSrcPort->text()); + proto->setFieldData( + UdpProtocol::udp_isOverrideSrcPort, + cbUdpSrcPortOverride->isChecked()); + proto->setFieldData( + UdpProtocol::udp_dstPort, + leUdpDstPort->text()); + proto->setFieldData( + UdpProtocol::udp_isOverrideDstPort, + cbUdpDstPortOverride->isChecked()); + + proto->setFieldData( + UdpProtocol::udp_totLen, + leUdpLength->text()); + proto->setFieldData( + UdpProtocol::udp_isOverrideTotLen, + cbUdpLengthOverride->isChecked()); + + proto->setFieldData( + UdpProtocol::udp_cksum, + hexStrToUInt(leUdpCksum->text())); + proto->setFieldData( + UdpProtocol::udp_isOverrideCksum, + cbUdpCksumOverride->isChecked()); +} + diff --git a/common/udpconfig.h b/common/udpconfig.h new file mode 100644 index 0000000..98b8ecb --- /dev/null +++ b/common/udpconfig.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _UDP_CONFIG_H +#define _UDP_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_udp.h" + +class UdpConfigForm : + public AbstractProtocolConfigForm, + private Ui::udp +{ + Q_OBJECT +public: + UdpConfigForm(QWidget *parent = 0); + virtual ~UdpConfigForm(); + + static UdpConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +}; + +#endif diff --git a/common/udppdml.cpp b/common/udppdml.cpp new file mode 100644 index 0000000..0cb1685 --- /dev/null +++ b/common/udppdml.cpp @@ -0,0 +1,53 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "udppdml.h" + +#include "udp.pb.h" + +PdmlUdpProtocol::PdmlUdpProtocol() +{ + ostProtoId_ = OstProto::Protocol::kUdpFieldNumber; + + fieldMap_.insert("udp.srcport", OstProto::Udp::kSrcPortFieldNumber); + fieldMap_.insert("udp.dstport", OstProto::Udp::kDstPortFieldNumber); + fieldMap_.insert("udp.length", OstProto::Udp::kTotlenFieldNumber); + fieldMap_.insert("udp.checksum_coverage", + OstProto::Udp::kTotlenFieldNumber); + fieldMap_.insert("udp.checksum", OstProto::Udp::kCksumFieldNumber); +} + +PdmlProtocol* PdmlUdpProtocol::createInstance() +{ + return new PdmlUdpProtocol(); +} + +void PdmlUdpProtocol::postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream* /*stream*/) +{ + OstProto::Udp *udp = pbProto->MutableExtension(OstProto::udp); + + qDebug("Udp: post\n"); + + udp->set_is_override_src_port(true); + udp->set_is_override_dst_port(true); + udp->set_is_override_totlen(true); + udp->set_is_override_cksum(true); +} + diff --git a/common/udppdml.h b/common/udppdml.h new file mode 100644 index 0000000..3ae1d6e --- /dev/null +++ b/common/udppdml.h @@ -0,0 +1,35 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _UDP_PDML_H +#define _UDP_PDML_H + +#include "pdmlprotocol.h" + +class PdmlUdpProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + virtual void postProtocolHandler(OstProto::Protocol *pbProto, + OstProto::Stream *stream); +protected: + PdmlUdpProtocol(); +}; + +#endif diff --git a/common/userscript.cpp b/common/userscript.cpp new file mode 100644 index 0000000..f00ab6c --- /dev/null +++ b/common/userscript.cpp @@ -0,0 +1,555 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "userscript.h" + +// +// -------------------- UserScriptProtocol -------------------- +// + +UserScriptProtocol::UserScriptProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent), + userProtocol_(this) +{ + isScriptValid_ = false; + errorLineNumber_ = 0; + + userProtocolScriptValue_ = engine_.newQObject(&userProtocol_); + engine_.globalObject().setProperty("protocol", userProtocolScriptValue_); + + QScriptValue meta = engine_.newQMetaObject(userProtocol_.metaObject()); + engine_.globalObject().setProperty("Protocol", meta); +} + +UserScriptProtocol::~UserScriptProtocol() +{ +} + +AbstractProtocol* UserScriptProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new UserScriptProtocol(stream, parent); +} + +quint32 UserScriptProtocol::protocolNumber() const +{ + return OstProto::Protocol::kUserScriptFieldNumber; +} + +void UserScriptProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::userScript)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void UserScriptProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::userScript)) + data.MergeFrom(protocol.GetExtension(OstProto::userScript)); + + evaluateUserScript(); +} + +QString UserScriptProtocol::name() const +{ + return QString("%1:{UserScript} [EXPERIMENTAL]").arg(userProtocol_.name()); +} + +QString UserScriptProtocol::shortName() const +{ + return QString("%1:{Script} [EXPERIMENTAL]").arg(userProtocol_.name()); +} + +quint32 UserScriptProtocol::protocolId(ProtocolIdType type) const +{ + QScriptValue userFunction; + QScriptValue userValue; + + if (!isScriptValid_) + goto _do_default; + + userFunction = userProtocolScriptValue_.property("protocolId"); + + if (!userFunction.isValid()) + goto _do_default; + + Q_ASSERT(userFunction.isFunction()); + + userValue = userFunction.call(QScriptValue(), + QScriptValueList() << QScriptValue(&engine_, type)); + + Q_ASSERT(userValue.isValid()); + Q_ASSERT(userValue.isNumber()); + + return userValue.toUInt32(); + +_do_default: + return AbstractProtocol::protocolId(type); +} + +int UserScriptProtocol::fieldCount() const +{ + return userScript_fieldCount; +} + +QVariant UserScriptProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case userScript_program: + + switch(attrib) + { + case FieldName: + return QString("UserProtocol"); + + case FieldValue: + case FieldTextValue: + return QString().fromStdString(data.program()); + + case FieldFrameValue: + { + if (!isScriptValid_) + return QByteArray(); + + QScriptValue userFunction = userProtocolScriptValue_.property( + "protocolFrameValue"); + + Q_ASSERT(userFunction.isValid()); + Q_ASSERT(userFunction.isFunction()); + + QScriptValue userValue = userFunction.call(QScriptValue(), + QScriptValueList() << QScriptValue(&engine_, streamIndex)); + + Q_ASSERT(userValue.isValid()); + Q_ASSERT(userValue.isArray()); + + QByteArray fv; + QList pktBuf; + + qScriptValueToSequence(userValue, pktBuf); + + fv.resize(pktBuf.size()); + for (int i = 0; i < pktBuf.size(); i++) + fv[i] = pktBuf.at(i) & 0xFF; + + return fv; + } + default: + break; + } + break; + + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool UserScriptProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case userScript_program: + { + data.set_program(value.toString().toStdString()); + evaluateUserScript(); + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} + +int UserScriptProtocol::protocolFrameSize(int streamIndex) const +{ + if (!isScriptValid_) + return 0; + + QScriptValue userFunction = userProtocolScriptValue_.property( + "protocolFrameSize"); + + Q_ASSERT(userFunction.isValid()); + Q_ASSERT(userFunction.isFunction()); + + QScriptValue userValue = userFunction.call(QScriptValue(), + QScriptValueList() << QScriptValue(&engine_, streamIndex)); + + Q_ASSERT(userValue.isNumber()); + + return userValue.toInt32(); +} + +bool UserScriptProtocol::isProtocolFrameValueVariable() const +{ + return userProtocol_.isProtocolFrameValueVariable(); +} + +bool UserScriptProtocol::isProtocolFrameSizeVariable() const +{ + return userProtocol_.isProtocolFrameSizeVariable(); +} + +int UserScriptProtocol::protocolFrameVariableCount() const +{ + return userProtocol_.protocolFrameVariableCount(); +} + +quint32 UserScriptProtocol::protocolFrameCksum(int streamIndex, + CksumType cksumType) const +{ + QScriptValue userFunction; + QScriptValue userValue; + + if (!isScriptValid_) + goto _do_default; + + userFunction = userProtocolScriptValue_.property("protocolFrameCksum"); + + qDebug("userscript protoFrameCksum(): isValid:%d/isFunc:%d", + userFunction.isValid(), userFunction.isFunction()); + + if (!userFunction.isValid()) + goto _do_default; + + Q_ASSERT(userFunction.isFunction()); + + userValue = userFunction.call(QScriptValue(), + QScriptValueList() << QScriptValue(&engine_, streamIndex) + << QScriptValue(&engine_, cksumType)); + + Q_ASSERT(userValue.isValid()); + Q_ASSERT(userValue.isNumber()); + + return userValue.toUInt32(); + +_do_default: + return AbstractProtocol::protocolFrameCksum(streamIndex, cksumType); +} + +void UserScriptProtocol::evaluateUserScript() const +{ + QScriptValue userFunction; + QScriptValue userValue; + QString property; + + isScriptValid_ = false; + errorLineNumber_ = userScriptLineCount(); + + // Reset all properties including the dynamic ones + userProtocol_.reset(); + userProtocolScriptValue_.setProperty("protocolFrameValue", QScriptValue()); + userProtocolScriptValue_.setProperty("protocolFrameSize", QScriptValue()); + userProtocolScriptValue_.setProperty("protocolFrameCksum", QScriptValue()); + userProtocolScriptValue_.setProperty("protocolId", QScriptValue()); + + engine_.evaluate(fieldData(userScript_program, FieldValue).toString()); + if (engine_.hasUncaughtException()) + goto _error_exception; + + // Validate protocolFrameValue() + property = QString("protocolFrameValue"); + userFunction = userProtocolScriptValue_.property(property); + + qDebug("userscript property %s: isValid:%d/isFunc:%d", + property.toAscii().constData(), + userFunction.isValid(), userFunction.isFunction()); + + if (!userFunction.isValid()) + { + errorText_ = property + QString(" not set"); + goto _error_exit; + } + + if (!userFunction.isFunction()) + { + errorText_ = property + QString(" is not a function"); + goto _error_exit; + } + + userValue = userFunction.call(); + if (engine_.hasUncaughtException()) + goto _error_exception; + + qDebug("userscript property %s return value: isValid:%d/isArray:%d", + property.toAscii().constData(), + userValue.isValid(), userValue.isArray()); + + if (!userValue.isArray()) + { + errorText_ = property + QString(" does not return an array"); + goto _error_exit; + } + + // Validate protocolFrameSize() + property = QString("protocolFrameSize"); + userFunction = userProtocolScriptValue_.property(property); + + qDebug("userscript property %s: isValid:%d/isFunc:%d", + property.toAscii().constData(), + userFunction.isValid(), userFunction.isFunction()); + + if (!userFunction.isValid()) + { + errorText_ = property + QString(" not set"); + goto _error_exit; + } + + if (!userFunction.isFunction()) + { + errorText_ = property + QString(" is not a function"); + goto _error_exit; + } + + userValue = userFunction.call(); + if (engine_.hasUncaughtException()) + goto _error_exception; + + qDebug("userscript property %s return value: isValid:%d/isNumber:%d", + property.toAscii().constData(), + userValue.isValid(), userValue.isNumber()); + + if (!userValue.isNumber()) + { + errorText_ = property + QString(" does not return a number"); + goto _error_exit; + } + + // Validate protocolFrameCksum() [optional] + property = QString("protocolFrameCksum"); + userFunction = userProtocolScriptValue_.property(property); + + qDebug("userscript property %s: isValid:%d/isFunc:%d", + property.toAscii().constData(), + userFunction.isValid(), userFunction.isFunction()); + + if (!userFunction.isValid()) + goto _skip_cksum; + + if (!userFunction.isFunction()) + { + errorText_ = property + QString(" is not a function"); + goto _error_exit; + } + + userValue = userFunction.call(); + if (engine_.hasUncaughtException()) + goto _error_exception; + + qDebug("userscript property %s return value: isValid:%d/isNumber:%d", + property.toAscii().constData(), + userValue.isValid(), userValue.isNumber()); + + if (!userValue.isNumber()) + { + errorText_ = property + QString(" does not return a number"); + goto _error_exit; + } + + +_skip_cksum: + // Validate protocolId() [optional] + property = QString("protocolId"); + userFunction = userProtocolScriptValue_.property(property); + + qDebug("userscript property %s: isValid:%d/isFunc:%d", + property.toAscii().constData(), + userFunction.isValid(), userFunction.isFunction()); + + if (!userFunction.isValid()) + goto _skip_protocol_id; + + if (!userFunction.isFunction()) + { + errorText_ = property + QString(" is not a function"); + goto _error_exit; + } + + userValue = userFunction.call(); + if (engine_.hasUncaughtException()) + goto _error_exception; + + qDebug("userscript property %s return value: isValid:%d/isNumber:%d", + property.toAscii().constData(), + userValue.isValid(), userValue.isNumber()); + + if (!userValue.isNumber()) + { + errorText_ = property + QString(" does not return a number"); + goto _error_exit; + } + + +_skip_protocol_id: + errorText_ = QString(""); + isScriptValid_ = true; + return; + +_error_exception: + errorLineNumber_ = engine_.uncaughtExceptionLineNumber(); + errorText_ = engine_.uncaughtException().toString(); + +_error_exit: + userProtocol_.reset(); + return; +} + +bool UserScriptProtocol::isScriptValid() const +{ + return isScriptValid_; +} + +int UserScriptProtocol::userScriptErrorLineNumber() const +{ + return errorLineNumber_; +} + +QString UserScriptProtocol::userScriptErrorText() const +{ + return errorText_; +} + +int UserScriptProtocol::userScriptLineCount() const +{ + return fieldData(userScript_program, FieldValue).toString().count( + QChar('\n')) + 1; +} + +// +// -------------------- UserProtocol -------------------- +// + +UserProtocol::UserProtocol(AbstractProtocol *parent) + : parent_ (parent) +{ + reset(); +} + +void UserProtocol::reset() +{ + name_ = QString(); + protocolFrameValueVariable_ = false; + protocolFrameSizeVariable_ = false; + protocolFrameVariableCount_ = 1; +} + +QString UserProtocol::name() const +{ + return name_; +} + +void UserProtocol::setName(QString &name) +{ + name_ = name; +} + +bool UserProtocol::isProtocolFrameValueVariable() const +{ + return protocolFrameValueVariable_; +} + +void UserProtocol::setProtocolFrameValueVariable(bool variable) +{ + protocolFrameValueVariable_ = variable; +} + +bool UserProtocol::isProtocolFrameSizeVariable() const +{ + return protocolFrameSizeVariable_; +} + +void UserProtocol::setProtocolFrameSizeVariable(bool variable) +{ + protocolFrameSizeVariable_ = variable; +} + +int UserProtocol::protocolFrameVariableCount() const +{ + return protocolFrameVariableCount_; +} + +void UserProtocol::setProtocolFrameVariableCount(int count) +{ + protocolFrameVariableCount_ = count; +} + +quint32 UserProtocol::payloadProtocolId(UserProtocol::ProtocolIdType type) const +{ + return parent_->payloadProtocolId( + static_cast(type)); +} + +int UserProtocol::protocolFrameOffset(int streamIndex) const +{ + return parent_->protocolFrameOffset(streamIndex); +} + +int UserProtocol::protocolFramePayloadSize(int streamIndex) const +{ + return parent_->protocolFramePayloadSize(streamIndex); +} + +bool UserProtocol::isProtocolFramePayloadValueVariable() const +{ + return parent_->isProtocolFramePayloadValueVariable(); +} + +bool UserProtocol::isProtocolFramePayloadSizeVariable() const +{ + return parent_->isProtocolFramePayloadSizeVariable(); +} + +int UserProtocol::protocolFramePayloadVariableCount() const +{ + return parent_->protocolFramePayloadVariableCount(); +} + +quint32 UserProtocol::protocolFrameHeaderCksum(int streamIndex, + AbstractProtocol::CksumType cksumType) const +{ + return parent_->protocolFrameHeaderCksum(streamIndex, cksumType); +} + +quint32 UserProtocol::protocolFramePayloadCksum(int streamIndex, + AbstractProtocol::CksumType cksumType) const +{ + quint32 cksum; + + cksum = parent_->protocolFramePayloadCksum(streamIndex, cksumType); + qDebug("UserProto:%s = %d", __FUNCTION__, cksum); + return cksum; +} + +/* vim: set shiftwidth=4 tabstop=8 softtabstop=4 expandtab: */ diff --git a/common/userscript.h b/common/userscript.h new file mode 100644 index 0000000..f6bf6ef --- /dev/null +++ b/common/userscript.h @@ -0,0 +1,162 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _USER_SCRIPT_H +#define _USER_SCRIPT_H + +#include "abstractprotocol.h" +#include "userscript.pb.h" + +#include +#include + +class UserScriptProtocol; + +class UserProtocol : public QObject +{ + Q_OBJECT; + Q_ENUMS(ProtocolIdType); + Q_ENUMS(CksumType); + + Q_PROPERTY(QString name READ name WRITE setName); + Q_PROPERTY(bool protocolFrameValueVariable + READ isProtocolFrameValueVariable + WRITE setProtocolFrameValueVariable); + Q_PROPERTY(bool protocolFrameSizeVariable + READ isProtocolFrameSizeVariable + WRITE setProtocolFrameSizeVariable); + Q_PROPERTY(int protocolFrameVariableCount + READ protocolFrameVariableCount + WRITE setProtocolFrameVariableCount); + +public: + enum ProtocolIdType + { + ProtocolIdLlc = AbstractProtocol::ProtocolIdLlc, + ProtocolIdEth = AbstractProtocol::ProtocolIdEth, + ProtocolIdIp = AbstractProtocol::ProtocolIdIp, + ProtocolIdTcpUdp = AbstractProtocol::ProtocolIdTcpUdp + }; + + enum CksumType + { + CksumIp = AbstractProtocol::CksumIp, + CksumIpPseudo = AbstractProtocol::CksumIpPseudo, + CksumTcpUdp = AbstractProtocol::CksumTcpUdp + }; + + UserProtocol(AbstractProtocol *parent); + +public slots: + void reset(); + + QString name() const; + void setName(QString &name); + + bool isProtocolFrameValueVariable() const; + void setProtocolFrameValueVariable(bool variable); + bool isProtocolFrameSizeVariable() const; + void setProtocolFrameSizeVariable(bool variable); + int protocolFrameVariableCount() const; + void setProtocolFrameVariableCount(int count); + + quint32 payloadProtocolId(UserProtocol::ProtocolIdType type) const; + int protocolFrameOffset(int streamIndex = 0) const; + int protocolFramePayloadSize(int streamIndex = 0) const; + + bool isProtocolFramePayloadValueVariable() const; + bool isProtocolFramePayloadSizeVariable() const; + int protocolFramePayloadVariableCount() const; + + quint32 protocolFrameHeaderCksum(int streamIndex = 0, + AbstractProtocol::CksumType cksumType = AbstractProtocol::CksumIp) const; + quint32 protocolFramePayloadCksum(int streamIndex = 0, + AbstractProtocol::CksumType cksumType = AbstractProtocol::CksumIp) const; + +private: + AbstractProtocol *parent_; + + QString name_; + bool protocolFrameValueVariable_; + bool protocolFrameSizeVariable_; + int protocolFrameVariableCount_; +}; + +class UserScriptProtocol : public AbstractProtocol +{ +public: + enum userScriptfield + { + // Frame Fields + userScript_program = 0, + + userScript_fieldCount + }; + + UserScriptProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~UserScriptProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual quint32 protocolId(ProtocolIdType type) const; + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + + virtual int protocolFrameSize(int streamIndex = 0) const; + + virtual bool isProtocolFrameValueVariable() const; + virtual bool isProtocolFrameSizeVariable() const; + virtual int protocolFrameVariableCount() const; + + virtual quint32 protocolFrameCksum(int streamIndex = 0, + CksumType cksumType = CksumIp) const; + + void evaluateUserScript() const; + bool isScriptValid() const; + int userScriptErrorLineNumber() const; + QString userScriptErrorText() const; + +private: + int userScriptLineCount() const; + + OstProto::UserScript data; + + mutable QScriptEngine engine_; + mutable UserProtocol userProtocol_; + mutable QScriptValue userProtocolScriptValue_; + + mutable bool isScriptValid_; + mutable int errorLineNumber_; + mutable QString errorText_; +}; + +#endif diff --git a/common/userscript.proto b/common/userscript.proto new file mode 100644 index 0000000..aa1e195 --- /dev/null +++ b/common/userscript.proto @@ -0,0 +1,33 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Sample Protocol +message UserScript { + + optional string program = 1; + +} + +extend Protocol { + optional UserScript userScript = 103; +} diff --git a/common/userscript.ui b/common/userscript.ui new file mode 100644 index 0000000..e18e024 --- /dev/null +++ b/common/userscript.ui @@ -0,0 +1,70 @@ + + UserScript + + + + 0 + 0 + 517 + 335 + + + + Form + + + + + + + + + + 10 + 0 + + + + QFrame::Panel + + + QFrame::Sunken + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + Unknown + + + + + + + + + + Compile + + + + + + + + diff --git a/common/userscriptconfig.cpp b/common/userscriptconfig.cpp new file mode 100644 index 0000000..85e0aba --- /dev/null +++ b/common/userscriptconfig.cpp @@ -0,0 +1,109 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "userscriptconfig.h" +#include "userscript.h" + +#include + +UserScriptConfigForm::UserScriptConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); + + // The protocol_ (UserScriptProtocol) is a dummy protocol internal + // to UserScriptConfigForm whose sole purpose is to "compile" the script + // so that the configForm widget can display the compilation result. + // It is *not* used for actual packet contents at any time + protocol_ = new UserScriptProtocol(NULL); + compileScript(); +} + +UserScriptConfigForm::~UserScriptConfigForm() +{ + delete protocol_; +} + +UserScriptConfigForm* UserScriptConfigForm::createInstance() +{ + return new UserScriptConfigForm; +} + +void UserScriptConfigForm::loadWidget(AbstractProtocol *proto) +{ + programEdit->setPlainText( + proto->fieldData( + UserScriptProtocol::userScript_program, + AbstractProtocol::FieldValue + ).toString()); + + compileScript(); +} + +void UserScriptConfigForm::storeWidget(AbstractProtocol *proto) +{ + proto->setFieldData( + UserScriptProtocol::userScript_program, + programEdit->toPlainText()); +} + +// +// ----- private methods +// +void UserScriptConfigForm::compileScript() +{ + // storeWidget() will save the updated userscript into + // the protocol_ which in turn triggers the protocol_ to + // compile it + storeWidget(protocol_); + if (protocol_->isScriptValid()) + { + statusLabel->setText(QString("Success")); + compileButton->setDisabled(true); + } + else + { + statusLabel->setText( + QString("Error: %1: %2").arg( + protocol_->userScriptErrorLineNumber()).arg( + protocol_->userScriptErrorText())); + compileButton->setEnabled(true); + } +} + +// +// ----- private slots +// +void UserScriptConfigForm::on_programEdit_textChanged() +{ + compileButton->setEnabled(true); +} + +void UserScriptConfigForm::on_compileButton_clicked(bool /*checked*/) +{ + compileScript(); + if (!protocol_->isScriptValid()) + { + QMessageBox::critical(this, "Error", + QString("%1: %2").arg( + protocol_->userScriptErrorLineNumber()).arg( + protocol_->userScriptErrorText())); + } +} + diff --git a/common/userscriptconfig.h b/common/userscriptconfig.h new file mode 100644 index 0000000..8285103 --- /dev/null +++ b/common/userscriptconfig.h @@ -0,0 +1,51 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _USER_SCRIPT_CONFIG_H +#define _USER_SCRIPT_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_userscript.h" + +class UserScriptProtocol; + +class UserScriptConfigForm : + public AbstractProtocolConfigForm, + private Ui::UserScript +{ + Q_OBJECT + +public: + UserScriptConfigForm(QWidget *parent = 0); + virtual ~UserScriptConfigForm(); + + static UserScriptConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); + +private: + void compileScript(); + UserScriptProtocol *protocol_; + +private slots: + void on_programEdit_textChanged(); + void on_compileButton_clicked(bool checked = false); +}; +#endif diff --git a/common/vlan.cpp b/common/vlan.cpp new file mode 100644 index 0000000..cb07cd2 --- /dev/null +++ b/common/vlan.cpp @@ -0,0 +1,270 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "vlan.h" + +VlanProtocol::VlanProtocol(StreamBase *stream, AbstractProtocol *parent) + : AbstractProtocol(stream, parent) +{ +} + +VlanProtocol::~VlanProtocol() +{ +} + +AbstractProtocol* VlanProtocol::createInstance(StreamBase *stream, + AbstractProtocol *parent) +{ + return new VlanProtocol(stream, parent); +} + +quint32 VlanProtocol::protocolNumber() const +{ + return OstProto::Protocol::kVlanFieldNumber; +} + +void VlanProtocol::protoDataCopyInto(OstProto::Protocol &protocol) const +{ + protocol.MutableExtension(OstProto::vlan)->CopyFrom(data); + protocol.mutable_protocol_id()->set_id(protocolNumber()); +} + +void VlanProtocol::protoDataCopyFrom(const OstProto::Protocol &protocol) +{ + if (protocol.protocol_id().id() == protocolNumber() && + protocol.HasExtension(OstProto::vlan)) + data.MergeFrom(protocol.GetExtension(OstProto::vlan)); +} + +QString VlanProtocol::name() const +{ + return QString("Vlan"); +} + +QString VlanProtocol::shortName() const +{ + return QString("Vlan"); +} + +int VlanProtocol::fieldCount() const +{ + return vlan_fieldCount; +} + +AbstractProtocol::FieldFlags VlanProtocol::fieldFlags(int index) const +{ + AbstractProtocol::FieldFlags flags; + + flags = AbstractProtocol::fieldFlags(index); + + switch (index) + { + case vlan_tpid: + case vlan_prio: + case vlan_cfiDei: + case vlan_vlanId: + break; + + // meta-fields + case vlan_isOverrideTpid: + flags &= ~FrameField; + flags |= MetaField; + break; + } + + return flags; +} + +QVariant VlanProtocol::fieldData(int index, FieldAttrib attrib, + int streamIndex) const +{ + switch (index) + { + case vlan_tpid: + { + quint16 tpid; + + tpid = data.is_override_tpid() ? data.tpid() : 0x8100; + + switch(attrib) + { + case FieldName: + return QString("Tag Protocol Id"); + case FieldValue: + return tpid; + case FieldTextValue: + return QString("0x%1").arg(tpid, 2, BASE_HEX, QChar('0')); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian(tpid, (uchar*) fv.data()); + return fv; + } + default: + break; + } + break; + } + + case vlan_prio: + { + uint prio = ((data.vlan_tag() >> 13) & 0x07); + + switch(attrib) + { + case FieldName: + return QString("Priority"); + case FieldValue: + return prio; + case FieldTextValue: + return QString("%1").arg(prio); + case FieldFrameValue: + return QByteArray(1, (char) prio); + case FieldBitSize: + return 3; + default: + break; + } + break; + } + + case vlan_cfiDei: + { + uint cfiDei = ((data.vlan_tag() >> 12) & 0x01); + + switch(attrib) + { + case FieldName: + return QString("CFI/DEI"); + case FieldValue: + return cfiDei; + case FieldTextValue: + return QString("%1").arg(cfiDei); + case FieldFrameValue: + return QByteArray(1, (char) cfiDei); + case FieldBitSize: + return 1; + default: + break; + } + break; + } + + case vlan_vlanId: + { + quint16 vlanId = (data.vlan_tag() & 0x0FFF); + + switch(attrib) + { + case FieldName: + return QString("VLAN Id"); + case FieldValue: + return vlanId; + case FieldTextValue: + return QString("%1").arg(vlanId); + case FieldFrameValue: + { + QByteArray fv; + fv.resize(2); + qToBigEndian((quint16) vlanId, (uchar*) fv.data()); + return fv; + } + case FieldBitSize: + return 12; + default: + break; + } + break; + } + // Meta fields + + case vlan_isOverrideTpid: + switch(attrib) + { + case FieldValue: return data.is_override_tpid(); + default: break; + } + break; + default: + break; + } + + return AbstractProtocol::fieldData(index, attrib, streamIndex); +} + +bool VlanProtocol::setFieldData(int index, const QVariant &value, + FieldAttrib attrib) +{ + bool isOk = false; + + if (attrib != FieldValue) + goto _exit; + + switch (index) + { + case vlan_tpid: + { + uint tpid = value.toUInt(&isOk); + if (isOk) + data.set_tpid(tpid); + break; + } + case vlan_prio: + { + uint prio = value.toUInt(&isOk); + if (isOk) + data.set_vlan_tag( + ((prio & 0x07) << 13) | (data.vlan_tag() & 0x1FFF)); + break; + } + case vlan_cfiDei: + { + uint cfiDei = value.toUInt(&isOk); + if (isOk) + data.set_vlan_tag( + ((cfiDei & 0x01) << 12) | (data.vlan_tag() & 0xEFFF)); + break; + } + case vlan_vlanId: + { + uint vlanId = value.toUInt(&isOk); + if (isOk) + data.set_vlan_tag( + (vlanId & 0x0FFF) | (data.vlan_tag() & 0xF000)); + break; + } + + // Meta-Fields + case vlan_isOverrideTpid: + { + bool override = value.toUInt(&isOk); + if (isOk) + data.set_is_override_tpid(override); + break; + } + default: + qFatal("%s: unimplemented case %d in switch", __PRETTY_FUNCTION__, + index); + break; + } + +_exit: + return isOk; +} diff --git a/common/vlan.h b/common/vlan.h new file mode 100644 index 0000000..26cf874 --- /dev/null +++ b/common/vlan.h @@ -0,0 +1,67 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _VLAN_H +#define _VLAN_H + +#include "abstractprotocol.h" +#include "vlan.pb.h" + +class VlanProtocol : public AbstractProtocol +{ +public: + enum Vlanfield + { + vlan_tpid, + vlan_prio, + vlan_cfiDei, + vlan_vlanId, + + // meta-fields + vlan_isOverrideTpid, + + vlan_fieldCount + }; + + VlanProtocol(StreamBase *stream, AbstractProtocol *parent = 0); + virtual ~VlanProtocol(); + + static AbstractProtocol* createInstance(StreamBase *stream, + AbstractProtocol *parent = 0); + virtual quint32 protocolNumber() const; + + virtual void protoDataCopyInto(OstProto::Protocol &protocol) const; + virtual void protoDataCopyFrom(const OstProto::Protocol &protocol); + + virtual QString name() const; + virtual QString shortName() const; + + virtual int fieldCount() const; + + virtual AbstractProtocol::FieldFlags fieldFlags(int index) const; + virtual QVariant fieldData(int index, FieldAttrib attrib, + int streamIndex = 0) const; + virtual bool setFieldData(int index, const QVariant &value, + FieldAttrib attrib = FieldValue); + +protected: + OstProto::Vlan data; +}; + +#endif diff --git a/common/vlan.proto b/common/vlan.proto new file mode 100644 index 0000000..0bfc2a0 --- /dev/null +++ b/common/vlan.proto @@ -0,0 +1,34 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; +message Vlan { + // VLAN presence/absence + optional bool is_override_tpid = 1; + + // VLAN values + optional uint32 tpid = 2; + optional uint32 vlan_tag = 3; // includes prio, cfi and vlanid +} + +extend Protocol { + optional Vlan vlan = 205; +} diff --git a/common/vlan.ui b/common/vlan.ui new file mode 100644 index 0000000..72e49c0 --- /dev/null +++ b/common/vlan.ui @@ -0,0 +1,181 @@ + + Vlan + + + + 0 + 0 + 274 + 106 + + + + Form + + + + + + true + + + Override TPID + + + + + + + Priority + + + + + + + CFI/DEI + + + + + + + VLAN + + + + + + + false + + + >HH HH; + + + + + + + + + + true + + + + 0 + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + + + + true + + + + 0 + + + + + 1 + + + + + + + + true + + + 0 + + + + + + + Qt::Horizontal + + + + 111 + 20 + + + + + + + + Qt::Vertical + + + + 20 + 51 + + + + + + + + + + cbTpidOverride + toggled(bool) + leTpid + setEnabled(bool) + + + 59 + 41 + + + 59 + 57 + + + + + diff --git a/common/vlanconfig.cpp b/common/vlanconfig.cpp new file mode 100644 index 0000000..35241bd --- /dev/null +++ b/common/vlanconfig.cpp @@ -0,0 +1,87 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "vlanconfig.h" +#include "vlan.h" + +VlanConfigForm::VlanConfigForm(QWidget *parent) + : AbstractProtocolConfigForm(parent) +{ + setupUi(this); +} + +VlanConfigForm::~VlanConfigForm() +{ +} + +VlanConfigForm* VlanConfigForm::createInstance() +{ + return new VlanConfigForm; +} + +void VlanConfigForm::loadWidget(AbstractProtocol *proto) +{ + cbTpidOverride->setChecked( + proto->fieldData( + VlanProtocol::vlan_isOverrideTpid, + AbstractProtocol::FieldValue + ).toBool()); + leTpid->setText(uintToHexStr( + proto->fieldData( + VlanProtocol::vlan_tpid, + AbstractProtocol::FieldValue) + .toUInt(), 2)); + cmbPrio->setCurrentIndex( + proto->fieldData( + VlanProtocol::vlan_prio, + AbstractProtocol::FieldValue) + .toUInt()); + cmbCfiDei->setCurrentIndex( + proto->fieldData( + VlanProtocol::vlan_cfiDei, + AbstractProtocol::FieldValue) + .toUInt()); + leVlanId->setText( + proto->fieldData( + VlanProtocol::vlan_vlanId, + AbstractProtocol::FieldValue) + .toString()); +} + +void VlanConfigForm::storeWidget(AbstractProtocol *proto) +{ + bool isOk; + + proto->setFieldData( + VlanProtocol::vlan_isOverrideTpid, + cbTpidOverride->isChecked()); + proto->setFieldData( + VlanProtocol::vlan_tpid, + leTpid->text().remove(QChar(' ')).toUInt(&isOk, BASE_HEX)); + proto->setFieldData( + VlanProtocol::vlan_prio, + cmbPrio->currentIndex()); + proto->setFieldData( + VlanProtocol::vlan_cfiDei, + cmbCfiDei->currentIndex()); + proto->setFieldData( + VlanProtocol::vlan_vlanId, + leVlanId->text()); +} + diff --git a/common/vlanconfig.h b/common/vlanconfig.h new file mode 100644 index 0000000..a761873 --- /dev/null +++ b/common/vlanconfig.h @@ -0,0 +1,41 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _VLAN_CONFIG_H +#define _VLAN_CONFIG_H + +#include "abstractprotocolconfig.h" +#include "ui_vlan.h" + +class VlanConfigForm : + public AbstractProtocolConfigForm, + private Ui::Vlan +{ + Q_OBJECT +public: + VlanConfigForm(QWidget *parent = 0); + virtual ~VlanConfigForm(); + + static VlanConfigForm* createInstance(); + + virtual void loadWidget(AbstractProtocol *proto); + virtual void storeWidget(AbstractProtocol *proto); +}; + +#endif diff --git a/common/vlanpdml.cpp b/common/vlanpdml.cpp new file mode 100644 index 0000000..722d038 --- /dev/null +++ b/common/vlanpdml.cpp @@ -0,0 +1,91 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "vlanpdml.h" + +#include "eth2.pb.h" +#include "vlan.pb.h" + +PdmlVlanProtocol::PdmlVlanProtocol() +{ + ostProtoId_ = OstProto::Protocol::kVlanFieldNumber; +} + +PdmlProtocol* PdmlVlanProtocol::createInstance() +{ + return new PdmlVlanProtocol(); +} + +void PdmlVlanProtocol::preProtocolHandler(QString /*name*/, + const QXmlStreamAttributes& /*attributes*/, int /*expectedPos*/, + OstProto::Protocol *pbProto, OstProto::Stream *stream) +{ + OstProto::Vlan *vlan = pbProto->MutableExtension(OstProto::vlan); + + vlan->set_tpid(0x8100); + vlan->set_is_override_tpid(true); + + // If a eth2 protocol precedes vlan, we remove the eth2 protocol + // 'coz the eth2.etherType is actually the vlan.tpid + // + // We assume that the current protocol is the last in the stream + int index = stream->protocol_size() - 1; + if ((index > 1) + && (stream->protocol(index).protocol_id().id() + == OstProto::Protocol::kVlanFieldNumber) + && (stream->protocol(index - 1).protocol_id().id() + == OstProto::Protocol::kEth2FieldNumber)) + { + stream->mutable_protocol()->SwapElements(index, index - 1); + Q_ASSERT(stream->protocol(index).protocol_id().id() + == OstProto::Protocol::kEth2FieldNumber); + stream->mutable_protocol()->RemoveLast(); + } +} + +void PdmlVlanProtocol::unknownFieldHandler(QString name, int /*pos*/, + int /*size*/, const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream) +{ + if (name == "vlan.id") + { + bool isOk; + OstProto::Vlan *vlan = pbProto->MutableExtension(OstProto::vlan); + uint tag = attributes.value("unmaskedvalue").isEmpty() ? + attributes.value("value").toString().toUInt(&isOk, kBaseHex) : + attributes.value("unmaskedvalue").toString().toUInt(&isOk,kBaseHex); + + vlan->set_vlan_tag(tag); + } + else if (name == "vlan.etype") + { + OstProto::Protocol *proto = stream->add_protocol(); + + proto->mutable_protocol_id()->set_id( + OstProto::Protocol::kEth2FieldNumber); + + bool isOk; + OstProto::Eth2 *eth2 = proto->MutableExtension(OstProto::eth2); + + eth2->set_type(attributes.value("value") + .toString().toUInt(&isOk, kBaseHex)); + eth2->set_is_override_type(true); + } +} + diff --git a/common/vlanpdml.h b/common/vlanpdml.h new file mode 100644 index 0000000..6bab024 --- /dev/null +++ b/common/vlanpdml.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _VLAN_PDML_H +#define _VLAN_PDML_H + +#include "pdmlprotocol.h" + +class PdmlVlanProtocol : public PdmlProtocol +{ +public: + static PdmlProtocol* createInstance(); + + virtual void preProtocolHandler(QString name, + const QXmlStreamAttributes &attributes, int expectedPos, + OstProto::Protocol *pbProto, OstProto::Stream *stream); + virtual void unknownFieldHandler(QString name, int pos, int size, + const QXmlStreamAttributes &attributes, + OstProto::Protocol *pbProto, OstProto::Stream *stream); +protected: + PdmlVlanProtocol(); +}; + +#endif diff --git a/common/vlanstack.h b/common/vlanstack.h new file mode 100644 index 0000000..847ac3d --- /dev/null +++ b/common/vlanstack.h @@ -0,0 +1,30 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _VLAN_STACK_H +#define _VLAN_STACK_H + +#include "comboprotocol.h" +#include "svlan.h" +#include "vlan.h" + +typedef ComboProtocol VlanStackProtocol; + +#endif diff --git a/common/vlanstack.proto b/common/vlanstack.proto new file mode 100644 index 0000000..d6bacd4 --- /dev/null +++ b/common/vlanstack.proto @@ -0,0 +1,31 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +import "protocol.proto"; + +package OstProto; + +// Stacked VLAN (2 tags) +message VlanStack { + // Empty since this is a 'combo' protocol +} + +extend Protocol { + optional VlanStack vlanStack = 208; +} diff --git a/common/vlanstackconfig.h b/common/vlanstackconfig.h new file mode 100644 index 0000000..5203824 --- /dev/null +++ b/common/vlanstackconfig.h @@ -0,0 +1,38 @@ +/* +Copyright (C) 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _VLAN_STACK_CONFIG_H +#define _VLAN_STACK_CONFIG_H + +#include "comboprotocolconfig.h" + +#include "svlanconfig.h" +#include "vlanconfig.h" +#include "svlan.h" +#include "vlan.h" + +#include "protocol.pb.h" + +typedef ComboProtocolConfigForm < + OstProto::Protocol::kVlanStackFieldNumber, + SVlanConfigForm, VlanConfigForm, + SVlanProtocol, VlanProtocol + > VlanStackConfigForm; + +#endif diff --git a/extra/extra.pro b/extra/extra.pro new file mode 100644 index 0000000..48aa842 --- /dev/null +++ b/extra/extra.pro @@ -0,0 +1,3 @@ +TEMPLATE = subdirs +SUBDIRS = \ + qhexedit2 diff --git a/extra/qhexedit2/qhexedit2.pro b/extra/qhexedit2/qhexedit2.pro new file mode 100644 index 0000000..89479e7 --- /dev/null +++ b/extra/qhexedit2/qhexedit2.pro @@ -0,0 +1,12 @@ +TEMPLATE = lib +CONFIG += qt staticlib warn_on + +HEADERS = src/commands.h\ + src/qhexedit.h \ + src/qhexedit_p.h \ + src/xbytearray.h + +SOURCES = src/commands.cpp \ + src/qhexedit.cpp \ + src/qhexedit_p.cpp \ + src/xbytearray.cpp diff --git a/extra/qhexedit2/src/commands.cpp b/extra/qhexedit2/src/commands.cpp new file mode 100644 index 0000000..303091d --- /dev/null +++ b/extra/qhexedit2/src/commands.cpp @@ -0,0 +1,115 @@ +#include "commands.h" + +CharCommand::CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, QUndoCommand *parent) + : QUndoCommand(parent) +{ + _xData = xData; + _charPos = charPos; + _newChar = newChar; + _cmd = cmd; +} + +bool CharCommand::mergeWith(const QUndoCommand *command) +{ + const CharCommand *nextCommand = static_cast(command); + bool result = false; + + if (_cmd != remove) + { + if (nextCommand->_cmd == replace) + if (nextCommand->_charPos == _charPos) + { + _newChar = nextCommand->_newChar; + result = true; + } + } + return result; +} + +void CharCommand::undo() +{ + switch (_cmd) + { + case insert: + _xData->remove(_charPos, 1); + break; + case replace: + _xData->replace(_charPos, _oldChar); + _xData->setDataChanged(_charPos, _wasChanged); + break; + case remove: + _xData->insert(_charPos, _oldChar); + _xData->setDataChanged(_charPos, _wasChanged); + break; + } +} + +void CharCommand::redo() +{ + switch (_cmd) + { + case insert: + _xData->insert(_charPos, _newChar); + break; + case replace: + _oldChar = _xData->data()[_charPos]; + _wasChanged = _xData->dataChanged(_charPos); + _xData->replace(_charPos, _newChar); + break; + case remove: + _oldChar = _xData->data()[_charPos]; + _wasChanged = _xData->dataChanged(_charPos); + _xData->remove(_charPos, 1); + break; + } +} + + + +ArrayCommand::ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa, int len, QUndoCommand *parent) + : QUndoCommand(parent) +{ + _cmd = cmd; + _xData = xData; + _baPos = baPos; + _newBa = newBa; + _len = len; +} + +void ArrayCommand::undo() +{ + switch (_cmd) + { + case insert: + _xData->remove(_baPos, _newBa.length()); + break; + case replace: + _xData->replace(_baPos, _oldBa); + _xData->setDataChanged(_baPos, _wasChanged); + break; + case remove: + _xData->insert(_baPos, _oldBa); + _xData->setDataChanged(_baPos, _wasChanged); + break; + } +} + +void ArrayCommand::redo() +{ + switch (_cmd) + { + case insert: + _xData->insert(_baPos, _newBa); + break; + case replace: + _oldBa = _xData->data().mid(_baPos, _len); + _wasChanged = _xData->dataChanged(_baPos, _len); + _xData->replace(_baPos, _newBa); + break; + case remove: + _oldBa = _xData->data().mid(_baPos, _len); + _wasChanged = _xData->dataChanged(_baPos, _len); + _xData->remove(_baPos, _len); + break; + } +} diff --git a/extra/qhexedit2/src/commands.h b/extra/qhexedit2/src/commands.h new file mode 100644 index 0000000..9931b3f --- /dev/null +++ b/extra/qhexedit2/src/commands.h @@ -0,0 +1,70 @@ +#ifndef COMMANDS_H +#define COMMANDS_H + +/** \cond docNever */ + +#include + +#include "xbytearray.h" + +/*! CharCommand is a class to prived undo/redo functionality in QHexEdit. +A QUndoCommand represents a single editing action on a document. CharCommand +is responsable for manipulations on single chars. It can insert. replace and +remove characters. A manipulation stores allways to actions +1. redo (or do) action +2. undo action. + +CharCommand also supports command compression via mergeWidht(). This allows +the user to execute a undo command contation e.g. 3 steps in a single command. +If you for example insert a new byt "34" this means for the editor doing 3 +steps: insert a "00", replace it with "03" and the replace it with "34". These +3 steps are combined into a single step, insert a "34". +*/ +class CharCommand : public QUndoCommand +{ +public: + enum { Id = 1234 }; + enum Cmd {insert, remove, replace}; + + CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, + QUndoCommand *parent=0); + + void undo(); + void redo(); + bool mergeWith(const QUndoCommand *command); + int id() const { return Id; } + +private: + XByteArray * _xData; + int _charPos; + bool _wasChanged; + char _newChar; + char _oldChar; + Cmd _cmd; +}; + +/*! ArrayCommand provides undo/redo functionality for handling binary strings. It +can undo/redo insert, replace and remove binary strins (QByteArrays). +*/ +class ArrayCommand : public QUndoCommand +{ +public: + enum Cmd {insert, remove, replace}; + ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa=QByteArray(), int len=0, + QUndoCommand *parent=0); + void undo(); + void redo(); + +private: + Cmd _cmd; + XByteArray * _xData; + int _baPos; + int _len; + QByteArray _wasChanged; + QByteArray _newBa; + QByteArray _oldBa; +}; + +/** \endcond docNever */ + +#endif // COMMANDS_H diff --git a/extra/qhexedit2/src/license.txt b/extra/qhexedit2/src/license.txt new file mode 100644 index 0000000..f166cc5 --- /dev/null +++ b/extra/qhexedit2/src/license.txt @@ -0,0 +1,502 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +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 this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser 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 Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "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 +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY 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 +LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! \ No newline at end of file diff --git a/extra/qhexedit2/src/qhexedit.cpp b/extra/qhexedit2/src/qhexedit.cpp new file mode 100644 index 0000000..b6dd38d --- /dev/null +++ b/extra/qhexedit2/src/qhexedit.cpp @@ -0,0 +1,152 @@ +#include + +#include "qhexedit.h" + + +QHexEdit::QHexEdit(QWidget *parent) : QScrollArea(parent) +{ + qHexEdit_p = new QHexEditPrivate(this); + setWidget(qHexEdit_p); + setWidgetResizable(true); + + connect(qHexEdit_p, SIGNAL(currentAddressChanged(int)), this, SIGNAL(currentAddressChanged(int))); + connect(qHexEdit_p, SIGNAL(currentSizeChanged(int)), this, SIGNAL(currentSizeChanged(int))); + connect(qHexEdit_p, SIGNAL(dataChanged()), this, SIGNAL(dataChanged())); + connect(qHexEdit_p, SIGNAL(overwriteModeChanged(bool)), this, SIGNAL(overwriteModeChanged(bool))); + setFocusPolicy(Qt::NoFocus); +} + +void QHexEdit::insert(int i, const QByteArray & ba) +{ + qHexEdit_p->insert(i, ba); +} + +void QHexEdit::insert(int i, char ch) +{ + qHexEdit_p->insert(i, ch); +} + +void QHexEdit::remove(int pos, int len) +{ + qHexEdit_p->remove(pos, len); +} + +QString QHexEdit::toReadableString() +{ + return qHexEdit_p->toRedableString(); +} + +QString QHexEdit::selectionToReadableString() +{ + return qHexEdit_p->selectionToReadableString(); +} + +void QHexEdit::setAddressArea(bool addressArea) +{ + qHexEdit_p->setAddressArea(addressArea); +} + +void QHexEdit::redo() +{ + qHexEdit_p->redo(); +} + +void QHexEdit::undo() +{ + qHexEdit_p->undo(); +} + +void QHexEdit::setAddressWidth(int addressWidth) +{ + qHexEdit_p->setAddressWidth(addressWidth); +} + +void QHexEdit::setAsciiArea(bool asciiArea) +{ + qHexEdit_p->setAsciiArea(asciiArea); +} + +void QHexEdit::setHighlighting(bool mode) +{ + qHexEdit_p->setHighlighting(mode); +} + +void QHexEdit::setAddressOffset(int offset) +{ + qHexEdit_p->setAddressOffset(offset); +} + +int QHexEdit::addressOffset() +{ + return qHexEdit_p->addressOffset(); +} + +void QHexEdit::setData(const QByteArray &data) +{ + qHexEdit_p->setData(data); +} + +QByteArray QHexEdit::data() +{ + return qHexEdit_p->data(); +} + +void QHexEdit::setAddressAreaColor(const QColor &color) +{ + qHexEdit_p->setAddressAreaColor(color); +} + +QColor QHexEdit::addressAreaColor() +{ + return qHexEdit_p->addressAreaColor(); +} + +void QHexEdit::setHighlightingColor(const QColor &color) +{ + qHexEdit_p->setHighlightingColor(color); +} + +QColor QHexEdit::highlightingColor() +{ + return qHexEdit_p->highlightingColor(); +} + +void QHexEdit::setSelectionColor(const QColor &color) +{ + qHexEdit_p->setSelectionColor(color); +} + +QColor QHexEdit::selectionColor() +{ + return qHexEdit_p->selectionColor(); +} + +void QHexEdit::setOverwriteMode(bool overwriteMode) +{ + qHexEdit_p->setOverwriteMode(overwriteMode); +} + +bool QHexEdit::overwriteMode() +{ + return qHexEdit_p->overwriteMode(); +} + +void QHexEdit::setReadOnly(bool readOnly) +{ + qHexEdit_p->setReadOnly(readOnly); +} + +bool QHexEdit::isReadOnly() +{ + return qHexEdit_p->isReadOnly(); +} + +void QHexEdit::setFont(const QFont &font) +{ + qHexEdit_p->setFont(font); +} + +const QFont & QHexEdit::font() const +{ + return qHexEdit_p->font(); +} diff --git a/extra/qhexedit2/src/qhexedit.h b/extra/qhexedit2/src/qhexedit.h new file mode 100644 index 0000000..b5a9601 --- /dev/null +++ b/extra/qhexedit2/src/qhexedit.h @@ -0,0 +1,205 @@ +#ifndef QHEXEDIT_H +#define QHEXEDIT_H + +#include +#include "qhexedit_p.h" + +/** \mainpage +QHexEdit is a binary editor widget for Qt. + +\version Version 0.6.1 +\image html hexedit.png +*/ + + +/*! QHexEdit is a hex editor widget written in C++ for the Qt (Qt4) framework. +It is a simple editor for binary data, just like QPlainTextEdit is for text +data. There are sip configuration files included, so it is easy to create +bindings for PyQt and you can use this widget also in python. + +QHexEdit takes the data of a QByteArray (setData()) and shows it. You can use +the mouse or the keyboard to navigate inside the widget. If you hit the keys +(0..9, a..f) you will change the data. Changed data is highlighted and can be +accessed via data(). + +Normaly QHexEdit works in the overwrite Mode. You can set overwriteMode(false) +and insert data. In this case the size of data() increases. It is also possible +to delete bytes (del or backspace), here the size of data decreases. + +You can select data with keyboard hits or mouse movements. The copy-key will +copy the selected data into the clipboard. The cut-key copies also but delets +it afterwards. In overwrite mode, the paste function overwrites the content of +the (does not change the length) data. In insert mode, clipboard data will be +inserted. The clipboard content is expected in ASCII Hex notation. Unknown +characters will be ignored. + +QHexEdit comes with undo/redo functionality. All changes can be undone, by +pressing the undo-key (usually ctr-z). They can also be redone afterwards. +The undo/redo framework is cleared, when setData() sets up a new +content for the editor. + +This widget can only handle small amounts of data. The size has to be below 10 +megabytes, otherwise the scroll sliders ard not shown and you can't scroll any +more. +*/ + class QHexEdit : public QScrollArea +{ + Q_OBJECT + /*! Property data holds the content of QHexEdit. Call setData() to set the + content of QHexEdit, data() returns the actual content. + */ + Q_PROPERTY(QByteArray data READ data WRITE setData) + + /*! Property addressOffset is added to the Numbers of the Address Area. + A offset in the address area (left side) is sometimes usefull, whe you show + only a segment of a complete memory picture. With setAddressOffset() you set + this property - with addressOffset() you get the actual value. + */ + Q_PROPERTY(int addressOffset READ addressOffset WRITE setAddressOffset) + + /*! Property address area color sets (setAddressAreaColor()) the backgorund + color of address areas. You can also read the color (addressaAreaColor()). + */ + Q_PROPERTY(QColor addressAreaColor READ addressAreaColor WRITE setAddressAreaColor) + + /*! Property highlighting color sets (setHighlightingColor()) the backgorund + color of highlighted text areas. You can also read the color + (highlightingColor()). + */ + Q_PROPERTY(QColor highlightingColor READ highlightingColor WRITE setHighlightingColor) + + /*! Property selection color sets (setSelectionColor()) the backgorund + color of selected text areas. You can also read the color + (selectionColor()). + */ + Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor) + + /*! Porperty overwrite mode sets (setOverwriteMode()) or gets (overwriteMode()) the mode + in which the editor works. In overwrite mode the user will overwrite existing data. The + size of data will be constant. In insert mode the size will grow, when inserting + new data. + */ + Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode) + + /*! Porperty readOnly sets (setReadOnly()) or gets (isReadOnly) the mode + in which the editor works. In readonly mode the the user can only navigate + through the data and select data; modifying is not possible. This + property's default is false. + */ + Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly) + + /*! Set the font of the widget. Please use fixed width fonts like Mono or Courier.*/ + Q_PROPERTY(QFont font READ font WRITE setFont) + + +public: + /*! Creates an instance of QHexEdit. + \param parent Parent widget of QHexEdit. + */ + QHexEdit(QWidget *parent = 0); + + /*! Inserts a byte array. + \param i Index position, where to insert + \param ba byte array, which is to insert + In overwrite mode, the existing data will be overwritten, in insertmode ba will be + insertet and size of data grows. + */ + void insert(int i, const QByteArray & ba); + + /*! Inserts a char. + \param i Index position, where to insert + \param ch Char, which is to insert + In overwrite mode, the existing data will be overwritten, in insertmode ba will be + insertet and size of data grows. + */ + void insert(int i, char ch); + + /*! Removes len bytes from the content. + \param pos Index position, where to remove + \param len Amount of bytes to remove + In overwrite mode, the existing bytes will be overwriten with 0x00. + */ + void remove(int pos, int len=1); + + /*! Gives back a formatted image of the content of QHexEdit + */ + QString toReadableString(); + + /*! Gives back a formatted image of the selected content of QHexEdit + */ + QString selectionToReadableString(); + + /*! \cond docNever */ + void setAddressOffset(int offset); + int addressOffset(); + void setData(QByteArray const &data); + QByteArray data(); + void setAddressAreaColor(QColor const &color); + QColor addressAreaColor(); + void setHighlightingColor(QColor const &color); + QColor highlightingColor(); + void setSelectionColor(QColor const &color); + QColor selectionColor(); + void setOverwriteMode(bool); + bool overwriteMode(); + void setReadOnly(bool); + bool isReadOnly(); + const QFont &font() const; + void setFont(const QFont &); + /*! \endcond docNever */ + +public slots: + /*! Redoes the last operation. If there is no operation to redo, i.e. + there is no redo step in the undo/redo history, nothing happens. + */ + void redo(); + + /*! Set the minimum width of the address area. + \param addressWidth Width in characters. + */ + void setAddressWidth(int addressWidth); + + /*! Switch the address area on or off. + \param addressArea true (show it), false (hide it). + */ + void setAddressArea(bool addressArea); + + /*! Switch the ascii area on or off. + \param asciiArea true (show it), false (hide it). + */ + void setAsciiArea(bool asciiArea); + + /*! Switch the highlighting feature on or of. + \param mode true (show it), false (hide it). + */ + void setHighlighting(bool mode); + + /*! Undoes the last operation. If there is no operation to undo, i.e. + there is no undo step in the undo/redo history, nothing happens. + */ + void undo(); + +signals: + + /*! Contains the address, where the cursor is located. */ + void currentAddressChanged(int address); + + /*! Contains the size of the data to edit. */ + void currentSizeChanged(int size); + + /*! The signal is emited every time, the data is changed. */ + void dataChanged(); + + /*! The signal is emited every time, the overwrite mode is changed. */ + void overwriteModeChanged(bool state); + +private: + /*! \cond docNever */ + QHexEditPrivate *qHexEdit_p; + QHBoxLayout *layout; + QScrollArea *scrollArea; + /*! \endcond docNever */ +}; + +#endif + diff --git a/extra/qhexedit2/src/qhexedit_p.cpp b/extra/qhexedit2/src/qhexedit_p.cpp new file mode 100644 index 0000000..2f046bb --- /dev/null +++ b/extra/qhexedit2/src/qhexedit_p.cpp @@ -0,0 +1,800 @@ +#include + +#include "qhexedit_p.h" +#include "commands.h" + +const int HEXCHARS_IN_LINE = 47; +const int GAP_ADR_HEX = 10; +const int GAP_HEX_ASCII = 16; +const int BYTES_PER_LINE = 16; + +QHexEditPrivate::QHexEditPrivate(QScrollArea *parent) : QWidget(parent) +{ + _undoStack = new QUndoStack(this); + + _scrollArea = parent; + setAddressWidth(4); + setAddressOffset(0); + setAddressArea(true); + setAsciiArea(true); + setHighlighting(true); + setOverwriteMode(true); + setReadOnly(false); + setAddressAreaColor(QColor(0xd4, 0xd4, 0xd4, 0xff)); + setHighlightingColor(QColor(0xff, 0xff, 0x99, 0xff)); + setSelectionColor(QColor(0x6d, 0x9e, 0xff, 0xff)); + setFont(QFont("Courier", 10)); + + _size = 0; + resetSelection(0); + + setFocusPolicy(Qt::StrongFocus); + + connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor())); + _cursorTimer.setInterval(500); + _cursorTimer.start(); +} + +void QHexEditPrivate::setAddressOffset(int offset) +{ + _xData.setAddressOffset(offset); + adjust(); +} + +int QHexEditPrivate::addressOffset() +{ + return _xData.addressOffset(); +} + +void QHexEditPrivate::setData(const QByteArray &data) +{ + _xData.setData(data); + _undoStack->clear(); + adjust(); + setCursorPos(0); +} + +QByteArray QHexEditPrivate::data() +{ + return _xData.data(); +} + +void QHexEditPrivate::setAddressAreaColor(const QColor &color) +{ + _addressAreaColor = color; + update(); +} + +QColor QHexEditPrivate::addressAreaColor() +{ + return _addressAreaColor; +} + +void QHexEditPrivate::setHighlightingColor(const QColor &color) +{ + _highlightingColor = color; + update(); +} + +QColor QHexEditPrivate::highlightingColor() +{ + return _highlightingColor; +} + +void QHexEditPrivate::setSelectionColor(const QColor &color) +{ + _selectionColor = color; + update(); +} + +QColor QHexEditPrivate::selectionColor() +{ + return _selectionColor; +} + +void QHexEditPrivate::setReadOnly(bool readOnly) +{ + _readOnly = readOnly; +} + +bool QHexEditPrivate::isReadOnly() +{ + return _readOnly; +} + +XByteArray & QHexEditPrivate::xData() +{ + return _xData; +} + +void QHexEditPrivate::insert(int index, const QByteArray & ba) +{ + if (ba.length() > 0) + { + if (_overwriteMode) + { + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); + _undoStack->push(arrayCommand); + emit dataChanged(); + } + else + { + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::insert, index, ba, ba.length()); + _undoStack->push(arrayCommand); + emit dataChanged(); + } + } +} + +void QHexEditPrivate::insert(int index, char ch) +{ + QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::insert, index, ch); + _undoStack->push(charCommand); + emit dataChanged(); +} + +void QHexEditPrivate::remove(int index, int len) +{ + if (len > 0) + { + if (len == 1) + { + if (_overwriteMode) + { + QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, char(0)); + _undoStack->push(charCommand); + emit dataChanged(); + } + else + { + QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::remove, index, char(0)); + _undoStack->push(charCommand); + emit dataChanged(); + } + } + else + { + QByteArray ba = QByteArray(len, char(0)); + if (_overwriteMode) + { + QUndoCommand *arrayCommand = new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); + _undoStack->push(arrayCommand); + emit dataChanged(); + } + else + { + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::remove, index, ba, len); + _undoStack->push(arrayCommand); + emit dataChanged(); + } + } + } +} + +void QHexEditPrivate::replace(int index, char ch) +{ + QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, ch); + _undoStack->push(charCommand); + emit dataChanged(); +} + +void QHexEditPrivate::replace(int index, const QByteArray & ba) +{ + QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length()); + _undoStack->push(arrayCommand); + emit dataChanged(); +} + +void QHexEditPrivate::setAddressArea(bool addressArea) +{ + _addressArea = addressArea; + adjust(); + + setCursorPos(_cursorPosition); +} + +void QHexEditPrivate::setAddressWidth(int addressWidth) +{ + _xData.setAddressWidth(addressWidth); + + setCursorPos(_cursorPosition); +} + +void QHexEditPrivate::setAsciiArea(bool asciiArea) +{ + _asciiArea = asciiArea; + adjust(); +} + +void QHexEditPrivate::setFont(const QFont &font) +{ + QWidget::setFont(font); + adjust(); +} + +void QHexEditPrivate::setHighlighting(bool mode) +{ + _highlighting = mode; + update(); +} + +void QHexEditPrivate::setOverwriteMode(bool overwriteMode) +{ + _overwriteMode = overwriteMode; +} + +bool QHexEditPrivate::overwriteMode() +{ + return _overwriteMode; +} + +void QHexEditPrivate::redo() +{ + _undoStack->redo(); + emit dataChanged(); + setCursorPos(_cursorPosition); + update(); +} + +void QHexEditPrivate::undo() +{ + _undoStack->undo(); + emit dataChanged(); + setCursorPos(_cursorPosition); + update(); +} + +QString QHexEditPrivate::toRedableString() +{ + return _xData.toRedableString(); +} + + +QString QHexEditPrivate::selectionToReadableString() +{ + return _xData.toRedableString(getSelectionBegin(), getSelectionEnd()); +} + +void QHexEditPrivate::keyPressEvent(QKeyEvent *event) +{ + int charX = (_cursorX - _xPosHex) / _charWidth; + int posX = (charX / 3) * 2 + (charX % 3); + int posBa = (_cursorY / _charHeight) * BYTES_PER_LINE + posX / 2; + + +/*****************************************************************************/ +/* Cursor movements */ +/*****************************************************************************/ + + if (event->matches(QKeySequence::MoveToNextChar)) + { + setCursorPos(_cursorPosition + 1); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousChar)) + { + setCursorPos(_cursorPosition - 1); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToEndOfLine)) + { + setCursorPos(_cursorPosition | (2 * BYTES_PER_LINE -1)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfLine)) + { + setCursorPos(_cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE))); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousLine)) + { + setCursorPos(_cursorPosition - (2 * BYTES_PER_LINE)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToNextLine)) + { + setCursorPos(_cursorPosition + (2 * BYTES_PER_LINE)); + resetSelection(_cursorPosition); + } + + if (event->matches(QKeySequence::MoveToNextPage)) + { + setCursorPos(_cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToPreviousPage)) + { + setCursorPos(_cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE)); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToEndOfDocument)) + { + setCursorPos(_xData.size() * 2); + resetSelection(_cursorPosition); + } + if (event->matches(QKeySequence::MoveToStartOfDocument)) + { + setCursorPos(0); + resetSelection(_cursorPosition); + } + +/*****************************************************************************/ +/* Select commands */ +/*****************************************************************************/ + if (event->matches(QKeySequence::SelectAll)) + { + resetSelection(0); + setSelection(2*_xData.size() + 1); + } + if (event->matches(QKeySequence::SelectNextChar)) + { + int pos = _cursorPosition + 1; + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousChar)) + { + int pos = _cursorPosition - 1; + setSelection(pos); + setCursorPos(pos); + } + if (event->matches(QKeySequence::SelectEndOfLine)) + { + int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfLine)) + { + int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousLine)) + { + int pos = _cursorPosition - (2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectNextLine)) + { + int pos = _cursorPosition + (2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + + if (event->matches(QKeySequence::SelectNextPage)) + { + int pos = _cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectPreviousPage)) + { + int pos = _cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE); + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectEndOfDocument)) + { + int pos = _xData.size() * 2; + setCursorPos(pos); + setSelection(pos); + } + if (event->matches(QKeySequence::SelectStartOfDocument)) + { + int pos = 0; + setCursorPos(pos); + setSelection(pos); + } + +/*****************************************************************************/ +/* Edit Commands */ +/*****************************************************************************/ +if (!_readOnly) +{ + /* Hex input */ + int key = int(event->text()[0].toAscii()); + if ((key>='0' && key<='9') || (key>='a' && key <= 'f')) + { + if (getSelectionBegin() != getSelectionEnd()) + { + posBa = getSelectionBegin(); + remove(posBa, getSelectionEnd() - posBa); + setCursorPos(2*posBa); + resetSelection(2*posBa); + } + + // If insert mode, then insert a byte + if (_overwriteMode == false) + if ((charX % 3) == 0) + { + insert(posBa, char(0)); + } + + // Change content + if (_xData.size() > 0) + { + QByteArray hexValue = _xData.data().mid(posBa, 1).toHex(); + if ((charX % 3) == 0) + hexValue[0] = key; + else + hexValue[1] = key; + + replace(posBa, QByteArray().fromHex(hexValue)[0]); + + setCursorPos(_cursorPosition + 1); + resetSelection(_cursorPosition); + } + } + + /* Cut & Paste */ + if (event->matches(QKeySequence::Cut)) + { + QString result = QString(); + for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++) + { + result += _xData.data().mid(idx, 1).toHex() + " "; + if ((idx % 16) == 15) + result.append("\n"); + } + remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin()); + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(result); + setCursorPos(getSelectionBegin()); + resetSelection(getSelectionBegin()); + } + + if (event->matches(QKeySequence::Paste)) + { + QClipboard *clipboard = QApplication::clipboard(); + QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1()); + insert(_cursorPosition / 2, ba); + setCursorPos(_cursorPosition + 2 * ba.length()); + resetSelection(getSelectionBegin()); + } + + + /* Delete char */ + if (event->matches(QKeySequence::Delete)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + posBa = getSelectionBegin(); + remove(posBa, getSelectionEnd() - posBa); + setCursorPos(2*posBa); + resetSelection(2*posBa); + } + else + { + if (_overwriteMode) + replace(posBa, char(0)); + else + remove(posBa, 1); + } + } + + /* Backspace */ + if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier)) + { + if (getSelectionBegin() != getSelectionEnd()) + { + posBa = getSelectionBegin(); + remove(posBa, getSelectionEnd() - posBa); + setCursorPos(2*posBa); + resetSelection(2*posBa); + } + else + { + if (posBa > 0) + { + if (_overwriteMode) + replace(posBa - 1, char(0)); + else + remove(posBa - 1, 1); + setCursorPos(_cursorPosition - 2); + } + } + } + + /* undo */ + if (event->matches(QKeySequence::Undo)) + { + undo(); + } + + /* redo */ + if (event->matches(QKeySequence::Redo)) + { + redo(); + } + + } + + if (event->matches(QKeySequence::Copy)) + { + QString result = QString(); + for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++) + { + result += _xData.data().mid(idx, 1).toHex() + " "; + if ((idx % 16) == 15) + result.append('\n'); + } + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(result); + } + + // Switch between insert/overwrite mode + if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier)) + { + _overwriteMode = !_overwriteMode; + setCursorPos(_cursorPosition); + overwriteModeChanged(_overwriteMode); + } + + _scrollArea->ensureVisible(_cursorX, _cursorY + _charHeight/2, 3, _charHeight/2 + 2); + update(); +} + +void QHexEditPrivate::mouseMoveEvent(QMouseEvent * event) +{ + _blink = false; + update(); + int actPos = cursorPos(event->pos()); + setCursorPos(actPos); + setSelection(actPos); +} + +void QHexEditPrivate::mousePressEvent(QMouseEvent * event) +{ + _blink = false; + update(); + int cPos = cursorPos(event->pos()); + resetSelection(cPos); + setCursorPos(cPos); +} + +void QHexEditPrivate::paintEvent(QPaintEvent *event) +{ + QPainter painter(this); + + // draw some patterns if needed + painter.fillRect(event->rect(), this->palette().color(QPalette::Base)); + if (_addressArea) + painter.fillRect(QRect(_xPosAdr, event->rect().top(), _xPosHex - GAP_ADR_HEX + 2, height()), _addressAreaColor); + if (_asciiArea) + { + int linePos = _xPosAscii - (GAP_HEX_ASCII / 2); + painter.setPen(Qt::gray); + painter.drawLine(linePos, event->rect().top(), linePos, height()); + } + + painter.setPen(this->palette().color(QPalette::WindowText)); + + // calc position + int firstLineIdx = ((event->rect().top()/ _charHeight) - _charHeight) * BYTES_PER_LINE; + if (firstLineIdx < 0) + firstLineIdx = 0; + int lastLineIdx = ((event->rect().bottom() / _charHeight) + _charHeight) * BYTES_PER_LINE; + if (lastLineIdx > _xData.size()) + lastLineIdx = _xData.size(); + int yPosStart = ((firstLineIdx) / BYTES_PER_LINE) * _charHeight + _charHeight; + + // paint address area + if (_addressArea) + { + for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) + { + QString address = QString("%1") + .arg(lineIdx + _xData.addressOffset(), _xData.realAddressNumbers(), 16, QChar('0')); + painter.drawText(_xPosAdr, yPos, address); + } + } + + // paint hex area + QByteArray hexBa(_xData.data().mid(firstLineIdx, lastLineIdx - firstLineIdx + 1).toHex()); + QBrush highLighted = QBrush(_highlightingColor); + QPen colHighlighted = QPen(this->palette().color(QPalette::WindowText)); + QBrush selected = QBrush(_selectionColor); + QPen colSelected = QPen(Qt::white); + QPen colStandard = QPen(this->palette().color(QPalette::WindowText)); + + painter.setBackgroundMode(Qt::TransparentMode); + + for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) + { + QByteArray hex; + int xPos = _xPosHex; + for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++) + { + int posBa = lineIdx + colIdx; + if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa)) + { + painter.setBackground(selected); + painter.setBackgroundMode(Qt::OpaqueMode); + painter.setPen(colSelected); + } + else + { + if (_highlighting) + { + // hilight diff bytes + painter.setBackground(highLighted); + if (_xData.dataChanged(posBa)) + { + painter.setPen(colHighlighted); + painter.setBackgroundMode(Qt::OpaqueMode); + } + else + { + painter.setPen(colStandard); + painter.setBackgroundMode(Qt::TransparentMode); + } + } + } + + // render hex value + if (colIdx == 0) + { + hex = hexBa.mid((lineIdx - firstLineIdx) * 2, 2); + painter.drawText(xPos, yPos, hex); + xPos += 2 * _charWidth; + } else { + hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2).prepend(" "); + painter.drawText(xPos, yPos, hex); + xPos += 3 * _charWidth; + } + + } + } + painter.setBackgroundMode(Qt::TransparentMode); + painter.setPen(this->palette().color(QPalette::WindowText)); + + // paint ascii area + if (_asciiArea) + { + for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight) + { + int xPosAscii = _xPosAscii; + for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() and (colIdx < BYTES_PER_LINE)); colIdx++) + { + painter.drawText(xPosAscii, yPos, _xData.asciiChar(lineIdx + colIdx)); + xPosAscii += _charWidth; + } + } + } + + // paint cursor + if (_blink) + { + if (_overwriteMode) + painter.fillRect(_cursorX, _cursorY + _charHeight - 2, _charWidth, 2, this->palette().color(QPalette::WindowText)); + else + painter.fillRect(_cursorX, _cursorY, 2, _charHeight, this->palette().color(QPalette::WindowText)); + } + + if (_size != _xData.size()) + { + _size = _xData.size(); + emit currentSizeChanged(_size); + } +} + +void QHexEditPrivate::setCursorPos(int position) +{ + // delete cursor + _blink = false; + update(); + + // cursor in range? + if (_overwriteMode) + { + if (position > (_xData.size() * 2 - 1)) + position = _xData.size() * 2 - 1; + } else { + if (position > (_xData.size() * 2)) + position = _xData.size() * 2; + } + + if (position < 0) + position = 0; + + // calc position + _cursorPosition = position; + _cursorY = (position / (2 * BYTES_PER_LINE)) * _charHeight + 4; + int x = (position % (2 * BYTES_PER_LINE)); + _cursorX = (((x / 2) * 3) + (x % 2)) * _charWidth + _xPosHex; + + // immiadately draw cursor + _blink = true; + update(); + emit currentAddressChanged(_cursorPosition/2); +} + +int QHexEditPrivate::cursorPos(QPoint pos) +{ + int result = -1; + // find char under cursor + if ((pos.x() >= _xPosHex) and (pos.x() < (_xPosHex + HEXCHARS_IN_LINE * _charWidth))) + { + int x = (pos.x() - _xPosHex) / _charWidth; + if ((x % 3) == 0) + x = (x / 3) * 2; + else + x = ((x / 3) * 2) + 1; + int y = ((pos.y() - 3) / _charHeight) * 2 * BYTES_PER_LINE; + result = x + y; + } + return result; +} + +int QHexEditPrivate::cursorPos() +{ + return _cursorPosition; +} + +void QHexEditPrivate::resetSelection(int pos) +{ + if (pos < 0) + pos = 0; + pos = pos / 2; + _selectionInit = pos; + _selectionBegin = pos; + _selectionEnd = pos; +} + +void QHexEditPrivate::setSelection(int pos) +{ + if (pos < 0) + pos = 0; + pos = pos / 2; + if (pos >= _selectionInit) + { + _selectionEnd = pos; + _selectionBegin = _selectionInit; + } + else + { + _selectionBegin = pos; + _selectionEnd = _selectionInit; + } +} + +int QHexEditPrivate::getSelectionBegin() +{ + return _selectionBegin; +} + +int QHexEditPrivate::getSelectionEnd() +{ + return _selectionEnd; +} + + +void QHexEditPrivate::updateCursor() +{ + if (_blink) + _blink = false; + else + _blink = true; + update(_cursorX, _cursorY, _charWidth, _charHeight); +} + +void QHexEditPrivate::adjust() +{ + _charWidth = fontMetrics().width(QLatin1Char('9')); + _charHeight = fontMetrics().height(); + + _xPosAdr = 0; + if (_addressArea) + _xPosHex = _xData.realAddressNumbers()*_charWidth + GAP_ADR_HEX; + else + _xPosHex = 0; + _xPosAscii = _xPosHex + HEXCHARS_IN_LINE * _charWidth + GAP_HEX_ASCII; + + // tell QAbstractScollbar, how big we are + setMinimumHeight(((_xData.size()/16 + 1) * _charHeight) + 5); + setMinimumWidth(_xPosAscii + (BYTES_PER_LINE * _charWidth)); + + update(); +} diff --git a/extra/qhexedit2/src/qhexedit_p.h b/extra/qhexedit2/src/qhexedit_p.h new file mode 100644 index 0000000..b802af3 --- /dev/null +++ b/extra/qhexedit2/src/qhexedit_p.h @@ -0,0 +1,120 @@ +#ifndef QHEXEDIT_P_H +#define QHEXEDIT_P_H + +/** \cond docNever */ + + +#include +#include "xbytearray.h" + +class QHexEditPrivate : public QWidget +{ +Q_OBJECT + +public: + QHexEditPrivate(QScrollArea *parent); + + void setAddressAreaColor(QColor const &color); + QColor addressAreaColor(); + + void setAddressOffset(int offset); + int addressOffset(); + + void setCursorPos(int position); + int cursorPos(); + + void setData(QByteArray const &data); + QByteArray data(); + + void setHighlightingColor(QColor const &color); + QColor highlightingColor(); + + void setOverwriteMode(bool overwriteMode); + bool overwriteMode(); + + void setReadOnly(bool readOnly); + bool isReadOnly(); + + void setSelectionColor(QColor const &color); + QColor selectionColor(); + + XByteArray & xData(); + + void insert(int index, const QByteArray & ba); + void insert(int index, char ch); + void remove(int index, int len=1); + void replace(int index, char ch); + void replace(int index, const QByteArray & ba); + + void setAddressArea(bool addressArea); + void setAddressWidth(int addressWidth); + void setAsciiArea(bool asciiArea); + void setHighlighting(bool mode); + virtual void setFont(const QFont &font); + + void undo(); + void redo(); + + QString toRedableString(); + QString selectionToReadableString(); + +signals: + void currentAddressChanged(int address); + void currentSizeChanged(int size); + void dataChanged(); + void overwriteModeChanged(bool state); + +protected: + void keyPressEvent(QKeyEvent * event); + void mouseMoveEvent(QMouseEvent * event); + void mousePressEvent(QMouseEvent * event); + + void paintEvent(QPaintEvent *event); + + int cursorPos(QPoint pos); // calc cursorpos from graphics position. DOES NOT STORE POSITION + + void resetSelection(int pos); + void setSelection(int pos); // set min (if below init) or max (if greater init) + int getSelectionBegin(); + int getSelectionEnd(); + + +private slots: + void updateCursor(); + +private: + void adjust(); + + QColor _addressAreaColor; + QColor _highlightingColor; + QColor _selectionColor; + QScrollArea *_scrollArea; + QTimer _cursorTimer; + QUndoStack *_undoStack; + + XByteArray _xData; // Hält den Inhalt des Hex Editors + + bool _blink; // true: then cursor blinks + bool _renderingRequired; // Flag to store that rendering is necessary + bool _addressArea; // left area of QHexEdit + bool _asciiArea; // medium area + bool _highlighting; // highlighting of changed bytes + bool _overwriteMode; + bool _readOnly; // true: the user can only look and navigate + + int _charWidth, _charHeight; // char dimensions (dpendend on font) + int _cursorX, _cursorY; // graphics position of the cursor + int _cursorPosition; // charakter positioin in stream (on byte ends in to steps) + int _xPosAdr, _xPosHex, _xPosAscii; // graphics x-position of the areas + + int _selectionBegin; // First selected char + int _selectionEnd; // Last selected char + int _selectionInit; // That's, where we pressed the mouse button + + int _size; +}; + +/** \endcond docNever */ + +#endif + diff --git a/extra/qhexedit2/src/xbytearray.cpp b/extra/qhexedit2/src/xbytearray.cpp new file mode 100644 index 0000000..ec8bf3d --- /dev/null +++ b/extra/qhexedit2/src/xbytearray.cpp @@ -0,0 +1,167 @@ +#include "xbytearray.h" + +XByteArray::XByteArray() +{ + _oldSize = -99; + _addressNumbers = 4; + _addressOffset = 0; + +} + +int XByteArray::addressOffset() +{ + return _addressOffset; +} + +void XByteArray::setAddressOffset(int offset) +{ + _addressOffset = offset; +} + +int XByteArray::addressWidth() +{ + return _addressNumbers; +} + +void XByteArray::setAddressWidth(int width) +{ + if ((width >= 0) and (width<=6)) + { + _addressNumbers = width; + } +} + +QByteArray & XByteArray::data() +{ + return _data; +} + +void XByteArray::setData(QByteArray data) +{ + _data = data; + _changedData = QByteArray(data.length(), char(0)); +} + +bool XByteArray::dataChanged(int i) +{ + return bool(_changedData[i]); +} + +QByteArray XByteArray::dataChanged(int i, int len) +{ + return _changedData.mid(i, len); +} + +void XByteArray::setDataChanged(int i, bool state) +{ + _changedData[i] = char(state); +} + +void XByteArray::setDataChanged(int i, const QByteArray & state) +{ + int length = state.length(); + int len; + if ((i + length) > _changedData.length()) + len = _changedData.length() - i; + else + len = length; + _changedData.replace(i, len, state); +} + +int XByteArray::realAddressNumbers() +{ + if (_oldSize != _data.size()) + { + // is addressNumbers wide enought? + QString test = QString("%1") + .arg(_data.size() + _addressOffset, _addressNumbers, 16, QChar('0')); + _realAddressNumbers = test.size(); + } + return _realAddressNumbers; +} + +int XByteArray::size() +{ + return _data.size(); +} + +QByteArray & XByteArray::insert(int i, char ch) +{ + _data.insert(i, ch); + _changedData.insert(i, char(1)); + return _data; +} + +QByteArray & XByteArray::insert(int i, const QByteArray & ba) +{ + _data.insert(i, ba); + _changedData.insert(i, QByteArray(ba.length(), char(1))); + return _data; +} + +QByteArray & XByteArray::remove(int i, int len) +{ + _data.remove(i, len); + _changedData.remove(i, len); + return _data; +} + +QByteArray & XByteArray::replace(int index, char ch) +{ + _data[index] = ch; + _changedData[index] = char(1); + return _data; +} + +QByteArray & XByteArray::replace(int index, const QByteArray & ba) +{ + int len = ba.length(); + return replace(index, len, ba); +} + +QByteArray & XByteArray::replace(int index, int length, const QByteArray & ba) +{ + int len; + if ((index + length) > _data.length()) + len = _data.length() - index; + else + len = length; + _data.replace(index, len, ba.mid(0, len)); + _changedData.replace(index, len, QByteArray(len, char(1))); + return _data; +} + +QChar XByteArray::asciiChar(int index) +{ + char ch = _data[index]; + if ((ch < 0x20) or (ch > 0x7e)) + ch = '.'; + return QChar(ch); +} + +QString XByteArray::toRedableString(int start, int end) +{ + int adrWidth = realAddressNumbers(); + if (_addressNumbers > adrWidth) + adrWidth = _addressNumbers; + if (end < 0) + end = _data.size(); + + QString result; + for (int i=start; i < end; i += 16) + { + QString adrStr = QString("%1").arg(_addressOffset + i, adrWidth, 16, QChar('0')); + QString hexStr; + QString ascStr; + for (int j=0; j<16; j++) + { + if ((i + j) < _data.size()) + { + hexStr.append(" ").append(_data.mid(i+j, 1).toHex()); + ascStr.append(asciiChar(i+j)); + } + } + result += adrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n"; + } + return result; +} diff --git a/extra/qhexedit2/src/xbytearray.h b/extra/qhexedit2/src/xbytearray.h new file mode 100644 index 0000000..2b67c61 --- /dev/null +++ b/extra/qhexedit2/src/xbytearray.h @@ -0,0 +1,66 @@ +#ifndef XBYTEARRAY_H +#define XBYTEARRAY_H + +/** \cond docNever */ + +#include + +/*! XByteArray represents the content of QHexEcit. +XByteArray comprehend the data itself and informations to store if it was +changed. The QHexEdit component uses these informations to perform nice +rendering of the data + +XByteArray also provides some functionality to insert, replace and remove +single chars and QByteArras. Additionally some functions support rendering +and converting to readable strings. +*/ +class XByteArray +{ +public: + explicit XByteArray(); + + int addressOffset(); + void setAddressOffset(int offset); + + int addressWidth(); + void setAddressWidth(int width); + + QByteArray & data(); + void setData(QByteArray data); + + bool dataChanged(int i); + QByteArray dataChanged(int i, int len); + void setDataChanged(int i, bool state); + void setDataChanged(int i, const QByteArray & state); + + int realAddressNumbers(); + int size(); + + QByteArray & insert(int i, char ch); + QByteArray & insert(int i, const QByteArray & ba); + + QByteArray & remove(int pos, int len); + + QByteArray & replace(int index, char ch); + QByteArray & replace(int index, const QByteArray & ba); + QByteArray & replace(int index, int length, const QByteArray & ba); + + QChar asciiChar(int index); + QString toRedableString(int start=0, int end=-1); + +signals: + +public slots: + +private: + QByteArray _data; + QByteArray _changedData; + + int _addressNumbers; // wanted width of address area + int _addressOffset; // will be added to the real addres inside bytearray + int _realAddressNumbers; // real width of address area (can be greater then wanted width) + int _oldSize; // size of data +}; + +/** \endcond docNever */ +#endif // XBYTEARRAY_H diff --git a/install.pri b/install.pri new file mode 100644 index 0000000..fdb16e0 --- /dev/null +++ b/install.pri @@ -0,0 +1,14 @@ +# A custom install path prefix can be provided by passing PREFIX=/absolute/path +# to qmake; if one is not provided, we use the below defaults - +isEmpty(PREFIX) { + unix:PREFIX = "/usr/local/" + macx:PREFIX = "/Applications/" + win32:PREFIX = "../" +} +macx { + target.path = $$PREFIX/Ostinato +} else { + target.path = $$PREFIX/bin +} + +INSTALLS += target diff --git a/ost.pro b/ost.pro new file mode 100644 index 0000000..0e758eb --- /dev/null +++ b/ost.pro @@ -0,0 +1,11 @@ +TEMPLATE = subdirs +CONFIG += ordered +SUBDIRS = \ + extra \ + rpc/pbrpc.pro \ + common/ostproto.pro \ + common/ostprotogui.pro \ + server/drone.pro \ + client/ostinato.pro \ + binding/binding.pro + diff --git a/protobuf.pri b/protobuf.pri new file mode 100644 index 0000000..6316a38 --- /dev/null +++ b/protobuf.pri @@ -0,0 +1,42 @@ +# +# Qt qmake integration with Google Protocol Buffers compiler protoc +# +# To compile protocol buffers with qt qmake, specify PROTOS variable and +# include this file +# +# Example: +# PROTOS = a.proto b.proto +# include(protobuf.pri) +# +# By default protoc looks for .proto files (including the imported ones) in +# the current directory where protoc is run. If you need to include additional +# paths specify the PROTOPATH variable +# + +PROTOPATH += . +PROTOPATHS = +for(p, PROTOPATH):PROTOPATHS += --proto_path=$${p} + +protobuf_decl.name = protobuf header +protobuf_decl.input = PROTOS +protobuf_decl.output = ${QMAKE_FILE_BASE}.pb.h +#protobuf_decl.commands = protoc --cpp_out="." $${PROTOPATHS} ${QMAKE_FILE_NAME} +protobuf_decl.commands = protoc --cpp_out="." --python_out="../binding/protocols" $${PROTOPATHS} ${QMAKE_FILE_NAME} +protobuf_decl.variable_out = GENERATED_FILES +QMAKE_EXTRA_COMPILERS += protobuf_decl + +protobuf_impl.name = protobuf implementation +protobuf_impl.input = PROTOS +protobuf_impl.output = ${QMAKE_FILE_BASE}.pb.cc +protobuf_impl.depends = ${QMAKE_FILE_BASE}.pb.h +protobuf_impl.commands = $$escape_expand(\n) +protobuf_impl.variable_out = GENERATED_SOURCES +QMAKE_EXTRA_COMPILERS += protobuf_impl + +protobuf_py.name = protobuf python binding +protobuf_py.input = PROTOS +protobuf_py.output = ../binding/protocols/${QMAKE_FILE_BASE}_pb2.py +protobuf_py.commands = $$escape_expand(\n) +#protobuf_py.commands = protoc --python_out="../binding/protocols" $${PROTOPATHS} ${QMAKE_FILE_NAME} +protobuf_py.variable_out = GENERATED_FILES +QMAKE_EXTRA_COMPILERS += protobuf_py diff --git a/rpc/pbhelper.h b/rpc/pbhelper.h new file mode 100644 index 0000000..7ab80b3 --- /dev/null +++ b/rpc/pbhelper.h @@ -0,0 +1,170 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PB_HELPER_H +#define _PB_HELPER_H + +#include +#include + +#include + +#if 0 // not reqd. any longer? +class PbHelper +{ +public: + + // FIXME: Change msg from * to & + void ForceSetSingularDefault(::google::protobuf::Message *msg) + { + const ::google::protobuf::Descriptor *desc; + ::google::protobuf::Message::Reflection *refl; + + qDebug("In %s", __FUNCTION__); + + desc = msg->GetDescriptor(); + refl = msg->GetReflection(); + + for (int i=0; i < desc->field_count(); i++) + { + const ::google::protobuf::FieldDescriptor *f; + + f = desc->field(i); + + // Ensure field is singular and not already set + if (f->label() == + ::google::protobuf::FieldDescriptor::LABEL_REPEATED) + continue; + if (refl->HasField(f)) + continue; + + switch(f->type()) + { + case ::google::protobuf::FieldDescriptor::TYPE_DOUBLE: + refl->SetDouble(f, refl->GetDouble(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_FLOAT: + refl->SetFloat(f, refl->GetFloat(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_INT32: + case ::google::protobuf::FieldDescriptor::TYPE_SINT32: + case ::google::protobuf::FieldDescriptor::TYPE_SFIXED32: + refl->SetInt32(f, refl->GetInt32(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_INT64: + case ::google::protobuf::FieldDescriptor::TYPE_SINT64: + case ::google::protobuf::FieldDescriptor::TYPE_SFIXED64: + refl->SetInt64(f, refl->GetInt64(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_UINT32: + case ::google::protobuf::FieldDescriptor::TYPE_FIXED32: + refl->SetUInt32(f, refl->GetUInt32(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_UINT64: + case ::google::protobuf::FieldDescriptor::TYPE_FIXED64: + refl->SetUInt64(f, refl->GetUInt64(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_BOOL: + refl->SetBool(f, refl->GetBool(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_ENUM: + refl->SetEnum(f, refl->GetEnum(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_STRING: + case ::google::protobuf::FieldDescriptor::TYPE_BYTES: + refl->SetString(f, refl->GetString(f)); + break; + + case ::google::protobuf::FieldDescriptor::TYPE_MESSAGE: + case ::google::protobuf::FieldDescriptor::TYPE_GROUP: + ForceSetSingularDefault(refl->MutableMessage(f)); // recursion! + break; + + default: + qDebug("unhandled Field Type"); + break; + } + } + } + + bool update( + ::google::protobuf::Message *target, + ::google::protobuf::Message *source) + { + // FIXME(HI): Depracate: use MergeFrom() directly + qDebug("In %s", __FUNCTION__); + target->MergeFrom(*source); + return true; +#if 0 + ::google::protobuf::Message::Reflection *sourceRef; + ::google::protobuf::Message::Reflection *targetRef; + std::vector srcFieldList; + + + if (source->GetDescriptor()->full_name() != + target->GetDescriptor()->full_name()) + goto _error_exit; + + sourceRef = source->GetReflection(); + targetRef = target->GetReflection(); + + sourceRef->ListFields(&srcFieldList); + for (uint i=0; i < srcFieldList.size(); i++) + { + const ::google::protobuf::FieldDescriptor *srcField, *targetField; + + srcField = srcFieldList[i]; + targetField = target->GetDescriptor()->FindFieldByName( + srcField->name()); + + switch(targetField->type()) + { + case ::google::protobuf::FieldDescriptor::TYPE_UINT32: + targetRef->SetUInt32(targetField, + sourceRef->GetUInt32(srcField)); + break; + case ::google::protobuf::FieldDescriptor::TYPE_BOOL: + targetRef->SetBool(targetField, + sourceRef->GetBool(srcField)); + break; + case ::google::protobuf::FieldDescriptor::TYPE_STRING: + targetRef->SetString(targetField, + sourceRef->GetString(srcField)); + break; + default: + qDebug("unhandled Field Type"); + break; + } + } + _error_exit: + qDebug("%s: error!", __FUNCTION__); + return false; +#endif + } +}; +#endif +#endif diff --git a/rpc/pbqtio.h b/rpc/pbqtio.h new file mode 100644 index 0000000..33d36a4 --- /dev/null +++ b/rpc/pbqtio.h @@ -0,0 +1,42 @@ +#ifndef _PBQTIO_H +#define _PBQTIO_H + +#include + +class PbQtInputStream : public google::protobuf::io::CopyingInputStream +{ +public: + PbQtInputStream(QIODevice *dev) + : dev_(dev) {}; + int Read(void *buffer, int size) { + _top: + if (dev_->bytesAvailable()) + return dev_->read(static_cast(buffer), size); + else + if (dev_->waitForReadyRead(-1)) + goto _top; + else + return -1; //return dev_->atEnd() ? 0 : -1; + } + +private: + QIODevice *dev_; +}; + +class PbQtOutputStream : public google::protobuf::io::CopyingOutputStream +{ +public: + PbQtOutputStream(QIODevice *dev) + : dev_(dev) {}; + bool Write(const void *buffer, int size) { + if (dev_->write(static_cast(buffer), size) == size) + return true; + else + return false; + } + +private: + QIODevice *dev_; +}; + +#endif diff --git a/rpc/pbrpc.pro b/rpc/pbrpc.pro new file mode 100644 index 0000000..ff28d8a --- /dev/null +++ b/rpc/pbrpc.pro @@ -0,0 +1,7 @@ +TEMPLATE = lib +CONFIG += qt staticlib +QT += network +DEFINES += HAVE_REMOTE +LIBS += -lprotobuf +HEADERS += rpcserver.h rpcconn.h pbrpccontroller.h pbrpcchannel.h pbqtio.h +SOURCES += rpcserver.cpp rpcconn.cpp pbrpcchannel.cpp diff --git a/rpc/pbrpcchannel.cpp b/rpc/pbrpcchannel.cpp new file mode 100644 index 0000000..516a023 --- /dev/null +++ b/rpc/pbrpcchannel.cpp @@ -0,0 +1,390 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pbrpcchannel.h" +#include "pbqtio.h" + +#include + +static uchar msgBuf[4096]; + +PbRpcChannel::PbRpcChannel(QHostAddress ip, quint16 port) +{ + isPending = false; + pendingMethodId = -1; // don't care as long as isPending is false + + controller = NULL; + done = NULL; + response = NULL; + + mServerAddress = ip; + mServerPort = port; + mpSocket = new QTcpSocket(this); + + inStream = new google::protobuf::io::CopyingInputStreamAdaptor( + new PbQtInputStream(mpSocket)); + inStream->SetOwnsCopyingStream(true); + outStream = new google::protobuf::io::CopyingOutputStreamAdaptor( + new PbQtOutputStream(mpSocket)); + outStream->SetOwnsCopyingStream(true); + + // FIXME: Not quite sure why this ain't working! + // QMetaObject::connectSlotsByName(this); + + connect(mpSocket, SIGNAL(connected()), + this, SLOT(on_mpSocket_connected())); + connect(mpSocket, SIGNAL(disconnected()), + this, SLOT(on_mpSocket_disconnected())); + connect(mpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), + this, SLOT(on_mpSocket_stateChanged(QAbstractSocket::SocketState))); + connect(mpSocket, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(on_mpSocket_error(QAbstractSocket::SocketError))); + + connect(mpSocket, SIGNAL(readyRead()), + this, SLOT(on_mpSocket_readyRead())); + +} + +PbRpcChannel::~PbRpcChannel() +{ + delete inStream; + delete outStream; + delete mpSocket; +} + +void PbRpcChannel::establish() +{ + qDebug("In %s", __FUNCTION__); + + mpSocket->connectToHost(mServerAddress, mServerPort); +} + +void PbRpcChannel::establish(QHostAddress ip, quint16 port) +{ + mServerAddress = ip; + mServerPort = port; + establish(); +} + +void PbRpcChannel::tearDown() +{ + qDebug("In %s", __FUNCTION__); + + mpSocket->disconnectFromHost(); +} + +void PbRpcChannel::CallMethod( + const ::google::protobuf::MethodDescriptor *method, + ::google::protobuf::RpcController *controller, + const ::google::protobuf::Message *req, + ::google::protobuf::Message *response, + ::google::protobuf::Closure* done) +{ + char* msg = (char*) &msgBuf[0]; + int len; + bool ret; + + if (isPending) + { + RpcCall call; + qDebug("RpcChannel: queueing rpc since method %d is pending;<----\n " + "queued method = %d\n" + "queued message = \n%s\n---->", + pendingMethodId, method->index(), req->DebugString().c_str()); + + call.method = method; + call.controller = controller; + call.request = req; + call.response = response; + call.done = done; + + pendingCallList.append(call); + qDebug("pendingCallList size = %d", pendingCallList.size()); + + Q_ASSERT(pendingCallList.size() < 100); + + return; + } + + if (!req->IsInitialized()) + { + qWarning("RpcChannel: missing required fields in request <----"); + qDebug("req = \n%s", req->DebugString().c_str()); + qDebug("error = \n%s\n--->", req->InitializationErrorString().c_str()); + + controller->SetFailed("Required fields missing"); + done->Run(); + return; + } + + pendingMethodId = method->index(); + this->controller=controller; + this->done=done; + this->response=response; + isPending = true; + + len = req->ByteSize(); + *((quint16*)(msg+0)) = qToBigEndian(quint16(PB_MSG_TYPE_REQUEST)); // type + *((quint16*)(msg+2)) = qToBigEndian(quint16(method->index())); // method id + *((quint32*)(msg+4)) = qToBigEndian(quint32(len)); // len + + // Avoid printing stats since it happens every couple of seconds + if (pendingMethodId != 13) + { + qDebug("client(%s) sending %d bytes <----", __FUNCTION__, + PB_HDR_SIZE + len); + BUFDUMP(msg, PB_HDR_SIZE); + qDebug("method = %d\n req = \n%s\n---->", + method->index(), req->DebugString().c_str()); + } + + mpSocket->write(msg, PB_HDR_SIZE); + ret = req->SerializeToZeroCopyStream(outStream); + Q_ASSERT(ret == true); + Q_UNUSED(ret); + outStream->Flush(); +} + +void PbRpcChannel::on_mpSocket_readyRead() +{ + uchar *msg = (uchar*) &msgBuf; + int msgLen; + static bool parsing = false; + static quint16 type, method; + static quint32 len; + + //qDebug("%s: bytesAvail = %d", __FUNCTION__, mpSocket->bytesAvailable()); + + if (!parsing) + { + // Do we have an entire header? If not, we'll wait ... + if (mpSocket->bytesAvailable() < PB_HDR_SIZE) + { + qDebug("client: not enough data available for a complete header"); + return; + } + + msgLen = mpSocket->read((char*)msg, PB_HDR_SIZE); + + Q_ASSERT(msgLen == PB_HDR_SIZE); + Q_UNUSED(msgLen); + + type = qFromBigEndian(msg+0); + method = qFromBigEndian(msg+2); + len = qFromBigEndian(msg+4); + + //BUFDUMP(msg, PB_HDR_SIZE); + //qDebug("type = %hu, method = %hu, len = %u", type, method, len); + + parsing = true; + } + + switch (type) + { + case PB_MSG_TYPE_BINBLOB: + { + static quint32 cumLen = 0; + QIODevice *blob; + + blob = static_cast(controller)->binaryBlob(); + Q_ASSERT(blob != NULL); + + while ((cumLen < len) && mpSocket->bytesAvailable()) + { + int l; + + l = mpSocket->read((char*)msgBuf, sizeof(msgBuf)); + blob->write((char*)msgBuf, l); + cumLen += l; + } + + qDebug("%s: bin blob rcvd %d/%d", __PRETTY_FUNCTION__, cumLen, len); + + if (cumLen < len) + return; + + cumLen = 0; + + if (!isPending) + { + qWarning("not waiting for response"); + goto _error_exit2; + } + + if (pendingMethodId != method) + { + qWarning("invalid method id %d (expected = %d)", method, + pendingMethodId); + goto _error_exit2; + } + + break; + } + + case PB_MSG_TYPE_RESPONSE: + //qDebug("client(%s) rcvd %d bytes", __FUNCTION__, msgLen); + //BUFDUMP(msg, msgLen); + + if (!isPending) + { + qWarning("not waiting for response"); + goto _error_exit; + } + + if (pendingMethodId != method) + { + qWarning("invalid method id %d (expected = %d)", method, + pendingMethodId); + goto _error_exit; + } + + if (len) + response->ParseFromBoundedZeroCopyStream(inStream, len); + + // Avoid printing stats + if (method != 13) + { + qDebug("client(%s): Received Msg <---- ", __FUNCTION__); + qDebug("method = %d\nresp = \n%s\n---->", + method, response->DebugString().c_str()); + } + + if (!response->IsInitialized()) + { + qWarning("RpcChannel: missing required fields in response <----"); + qDebug("resp = \n%s", response->DebugString().c_str()); + qDebug("error = \n%s\n--->", + response->InitializationErrorString().c_str()); + + controller->SetFailed("Required fields missing"); + } + break; + + case PB_MSG_TYPE_ERROR: + { + static quint32 cumLen = 0; + static QByteArray error; + + while ((cumLen < len) && mpSocket->bytesAvailable()) + { + int l; + + l = mpSocket->read((char*)msgBuf, sizeof(msgBuf)); + error.append(QByteArray((char*)msgBuf,l)); + cumLen += l; + } + + qDebug("%s: error rcvd %d/%d", __PRETTY_FUNCTION__, cumLen, len); + + if (cumLen < len) + return; + + static_cast(controller)->SetFailed( + QString::fromUtf8(error, len)); + + cumLen = 0; + error.resize(0); + + if (!isPending) + { + qWarning("not waiting for response"); + goto _error_exit2; + } + + if (pendingMethodId != method) + { + qWarning("invalid method id %d (expected = %d)", method, + pendingMethodId); + goto _error_exit2; + } + + break; + } + + default: + qFatal("%s: unexpected type %d", __PRETTY_FUNCTION__, type); + goto _error_exit; + + } + + done->Run(); + + pendingMethodId = -1; + controller = NULL; + response = NULL; + isPending = false; + parsing = false; + + if (pendingCallList.size()) + { + RpcCall call = pendingCallList.takeFirst(); + qDebug("RpcChannel: executing queued method <----\n" + "method = %d\n" + "req = \n%s\n---->", + call.method->index(), call.request->DebugString().c_str()); + CallMethod(call.method, call.controller, call.request, call.response, + call.done); + } + + return; + +_error_exit: + inStream->Skip(len); +_error_exit2: + parsing = false; + qDebug("client(%s) discarding received msg <----", __FUNCTION__); + qDebug("method = %d\nreq = \n%s\n---->", + method, response->DebugString().c_str()); + return; +} + +void PbRpcChannel::on_mpSocket_stateChanged( + QAbstractSocket::SocketState socketState) +{ + qDebug("In %s", __FUNCTION__); + emit stateChanged(socketState); +} + +void PbRpcChannel::on_mpSocket_connected() +{ + qDebug("In %s", __FUNCTION__); + emit connected(); +} + +void PbRpcChannel::on_mpSocket_disconnected() +{ + qDebug("In %s", __FUNCTION__); + + pendingMethodId = -1; + controller = NULL; + response = NULL; + isPending = false; + // \todo convert parsing from static to data member + //parsing = false + pendingCallList.clear(); + + emit disconnected(); +} + +void PbRpcChannel::on_mpSocket_error(QAbstractSocket::SocketError socketError) +{ + qDebug("In %s", __FUNCTION__); + emit error(socketError); +} + diff --git a/rpc/pbrpcchannel.h b/rpc/pbrpcchannel.h new file mode 100644 index 0000000..e3f9096 --- /dev/null +++ b/rpc/pbrpcchannel.h @@ -0,0 +1,106 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PB_RPC_CHANNEL_H +#define _PB_RPC_CHANNEL_H + +#include +#include + +#include +#include +#include +#include + +#include "pbrpccommon.h" +#include "pbrpccontroller.h" + +class PbRpcChannel : public QObject, public ::google::protobuf::RpcChannel +{ + Q_OBJECT + + // If isPending is TRUE, then controller, done, response + // and pendingMethodId correspond to the last method called by + // the service stub + bool isPending; + int pendingMethodId; + + // controller, done, response are set to the corresponding values + // passed by the stub to CallMethod(). They are reset to NULL when + // we get a response back from the server in on_mpSocket_readyRead() + // after calling done->Run(). + + /*! \todo (MED) : change controller, done and response to references + instead of pointers? */ + ::google::protobuf::RpcController *controller; + ::google::protobuf::Closure *done; + ::google::protobuf::Message *response; + + typedef struct _RpcCall { + const ::google::protobuf::MethodDescriptor *method; + ::google::protobuf::RpcController *controller; + const ::google::protobuf::Message *request; + ::google::protobuf::Message *response; + ::google::protobuf::Closure *done; + } RpcCall; + QList pendingCallList; + + QHostAddress mServerAddress; + quint16 mServerPort; + QTcpSocket *mpSocket; + + ::google::protobuf::io::CopyingInputStreamAdaptor *inStream; + ::google::protobuf::io::CopyingOutputStreamAdaptor *outStream; + +public: + PbRpcChannel(QHostAddress ip, quint16 port); + ~PbRpcChannel(); + + void establish(); + void establish(QHostAddress ip, quint16 port); + void tearDown(); + + const QHostAddress& serverAddress() const { return mServerAddress; } + quint16 serverPort() const { return mServerPort; } + + QAbstractSocket::SocketState state() const + { return mpSocket->state(); } + + void CallMethod(const ::google::protobuf::MethodDescriptor *method, + ::google::protobuf::RpcController *controller, + const ::google::protobuf::Message *req, + ::google::protobuf::Message *response, + ::google::protobuf::Closure* done); + +signals: + void connected(); + void disconnected(); + void error(QAbstractSocket::SocketError socketError); + void stateChanged(QAbstractSocket::SocketState socketState); + +private slots: + void on_mpSocket_connected(); + void on_mpSocket_disconnected(); + void on_mpSocket_stateChanged(QAbstractSocket::SocketState socketState); + void on_mpSocket_error(QAbstractSocket::SocketError socketError); + + void on_mpSocket_readyRead(); +}; + +#endif diff --git a/rpc/pbrpccommon.h b/rpc/pbrpccommon.h new file mode 100644 index 0000000..07c8013 --- /dev/null +++ b/rpc/pbrpccommon.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PB_RPC_COMMON_H +#define _PB_RPC_COMMON_H + +// Print a HexDump +#define BUFDUMP(ptr, len) qDebug("%s", QString(QByteArray((char*)(ptr), \ + (len)).toHex()).toAscii().data()); + +/* +** RPC Header (8) +** - MSG_TYPE (2) +** - METHOD_ID (2) +** - LEN (4) [not including this header] +*/ +#define PB_HDR_SIZE 8 + +#define PB_MSG_TYPE_REQUEST 1 +#define PB_MSG_TYPE_RESPONSE 2 +#define PB_MSG_TYPE_BINBLOB 3 +#define PB_MSG_TYPE_ERROR 4 + +#endif diff --git a/rpc/pbrpccontroller.h b/rpc/pbrpccontroller.h new file mode 100644 index 0000000..788fc2b --- /dev/null +++ b/rpc/pbrpccontroller.h @@ -0,0 +1,88 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PB_RPC_CONTROLLER_H +#define _PB_RPC_CONTROLLER_H + +#include +#include + +class QIODevice; + +/*! +PbRpcController takes ownership of the 'request' and 'response' messages and +will delete them when it itself is destroyed +*/ +class PbRpcController : public ::google::protobuf::RpcController +{ +public: + PbRpcController(::google::protobuf::Message *request, + ::google::protobuf::Message *response) { + request_ = request; + response_ = response; + Reset(); + } + ~PbRpcController() { delete request_; delete response_; } + + ::google::protobuf::Message* request() { return request_; } + ::google::protobuf::Message* response() { return response_; } + + // Client Side Methods + void Reset() { + failed = false; + disconnect = false; + blob = NULL; + errStr = ""; + } + bool Failed() const { return failed; } + void StartCancel() { /*! \todo (MED) */} + std::string ErrorText() const { return errStr.toStdString(); } + + // Server Side Methods + void SetFailed(const QString &reason) + { failed = true; errStr = reason; qWarning("%s", qPrintable(errStr)); } + void SetFailed(const std::string &reason) + { SetFailed(QString::fromStdString(reason)); } + QString ErrorString() const { return errStr; } + bool IsCanceled() const { return false; }; + void NotifyOnCancel(::google::protobuf::Closure* /* callback */) { + /*! \todo (MED) */ + } + void TriggerDisconnect() { + disconnect = true; + } + bool Disconnect() const { + return disconnect; + } + + // srivatsp added + QIODevice* binaryBlob() { return blob; }; + void setBinaryBlob(QIODevice *binaryBlob) { blob = binaryBlob; }; + +private: + bool failed; + bool disconnect; + QIODevice *blob; + QString errStr; + ::google::protobuf::Message *request_; + ::google::protobuf::Message *response_; + +}; + +#endif diff --git a/rpc/rpcconn.cpp b/rpc/rpcconn.cpp new file mode 100644 index 0000000..f55a224 --- /dev/null +++ b/rpc/rpcconn.cpp @@ -0,0 +1,372 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "rpcconn.h" + +#include "pbqtio.h" +#include "pbrpccommon.h" +#include "pbrpccontroller.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +static QThreadStorage connId; + +RpcConnection::RpcConnection(int socketDescriptor, + ::google::protobuf::Service *service) + : socketDescriptor(socketDescriptor), + service(service) +{ + inStream = NULL; + outStream = NULL; + + isPending = false; + pendingMethodId = -1; // don't care as long as isPending is false + + isCompatCheckDone = false; +} + +RpcConnection::~RpcConnection() +{ + qDebug("destroying connection to %s: %d", + clientSock->peerAddress().toString().toAscii().constData(), + clientSock->peerPort()); + + // If still connected, disconnect + if (clientSock->state() != QAbstractSocket::UnconnectedState) { + clientSock->disconnectFromHost(); + clientSock->waitForDisconnected(); + } + + delete inStream; + delete outStream; + + delete clientSock; +} + +void RpcConnection::start() +{ + QString id = QString("[%1:%2] "); + clientSock = new QTcpSocket; + if (!clientSock->setSocketDescriptor(socketDescriptor)) { + qWarning("Unable to initialize TCP socket for incoming connection"); + return; + } + qDebug("clientSock Thread = %p", clientSock->thread()); + + connId.setLocalData(new QString(id.arg(clientSock->peerAddress().toString()) + .arg(clientSock->peerPort()))); + + qDebug("accepting new connection from %s: %d", + clientSock->peerAddress().toString().toAscii().constData(), + clientSock->peerPort()); + inStream = new google::protobuf::io::CopyingInputStreamAdaptor( + new PbQtInputStream(clientSock)); + inStream->SetOwnsCopyingStream(true); + outStream = new google::protobuf::io::CopyingOutputStreamAdaptor( + new PbQtOutputStream(clientSock)); + outStream->SetOwnsCopyingStream(true); + + connect(clientSock, SIGNAL(readyRead()), + this, SLOT(on_clientSock_dataAvail())); + connect(clientSock, SIGNAL(disconnected()), + this, SLOT(on_clientSock_disconnected())); + connect(clientSock, SIGNAL(error(QAbstractSocket::SocketError)), + this, SLOT(on_clientSock_error(QAbstractSocket::SocketError))); +} + +void RpcConnection::writeHeader(char* header, quint16 type, quint16 method, + quint32 length) +{ + *((quint16*)(header+0)) = qToBigEndian(type); + *((quint16*)(header+2)) = qToBigEndian(method); + *((quint32*)(header+4)) = qToBigEndian(length); +} + +void RpcConnection::sendRpcReply(PbRpcController *controller) +{ + google::protobuf::Message *response = controller->response(); + QIODevice *blob; + char msgBuf[PB_HDR_SIZE]; + char* const msg = &msgBuf[0]; + int len; + + if (controller->Failed()) + { + QByteArray err = controller->ErrorString().toUtf8(); + + qWarning("rpc failed (%s)", qPrintable(controller->ErrorString())); + len = err.size(); + writeHeader(msg, PB_MSG_TYPE_ERROR, pendingMethodId, len); + clientSock->write(msg, PB_HDR_SIZE); + clientSock->write(err.constData(), len); + + goto _exit; + } + + blob = controller->binaryBlob(); + if (blob) + { + len = blob->size(); + qDebug("is binary blob of len %d", len); + + writeHeader(msg, PB_MSG_TYPE_BINBLOB, pendingMethodId, len); + clientSock->write(msg, PB_HDR_SIZE); + + blob->seek(0); + while (!blob->atEnd()) + { + int l; + + len = blob->read(msg, sizeof(msgBuf)); + l = clientSock->write(msg, len); + Q_ASSERT(l == len); + Q_UNUSED(l); + } + + goto _exit; + } + + if (!response->IsInitialized()) + { + qWarning("response missing required fields!! <----"); + qDebug("response = \n%s" + "missing = \n%s---->", + response->DebugString().c_str(), + response->InitializationErrorString().c_str()); + qFatal("exiting"); + goto _exit; + } + + len = response->ByteSize(); + writeHeader(msg, PB_MSG_TYPE_RESPONSE, pendingMethodId, len); + + // Avoid printing stats since it happens once every couple of seconds + if (pendingMethodId != 13) + { + qDebug("Server(%s): sending %d bytes to client <----", + __FUNCTION__, len + PB_HDR_SIZE); + BUFDUMP(msg, 8); + qDebug("method = %d\nreq = \n%s---->", + pendingMethodId, response->DebugString().c_str()); + } + + clientSock->write(msg, PB_HDR_SIZE); + response->SerializeToZeroCopyStream(outStream); + outStream->Flush(); + + if (pendingMethodId == 15) + isCompatCheckDone = true; + +_exit: + if (controller->Disconnect()) + clientSock->disconnectFromHost(); + + delete controller; + isPending = false; +} + +void RpcConnection::on_clientSock_disconnected() +{ + qDebug("connection closed from %s: %d", + clientSock->peerAddress().toString().toAscii().constData(), + clientSock->peerPort()); + + deleteLater(); + emit closed(); +} + +void RpcConnection::on_clientSock_error(QAbstractSocket::SocketError socketError) +{ + qDebug("%s (%d)", clientSock->errorString().toAscii().constData(), + socketError); +} + +void RpcConnection::on_clientSock_dataAvail() +{ + uchar msg[PB_HDR_SIZE]; + int msgLen; + quint16 type, method; + quint32 len; + const ::google::protobuf::MethodDescriptor *methodDesc; + ::google::protobuf::Message *req, *resp; + PbRpcController *controller; + QString error; + bool disconnect = false; + + // Do we have enough bytes for a msg header? + // If yes, peek into the header and get msg length + if (clientSock->bytesAvailable() < PB_HDR_SIZE) + return; + + msgLen = clientSock->peek((char*)msg, PB_HDR_SIZE); + if (msgLen != PB_HDR_SIZE) { + qWarning("asked to peek %d bytes, was given only %d bytes", + PB_HDR_SIZE, msgLen); + return; + } + + len = qFromBigEndian(&msg[4]); + + // Is the full msg available to read? If not, wait till such time + if (clientSock->bytesAvailable() < (PB_HDR_SIZE+len)) + return; + + msgLen = clientSock->read((char*)msg, PB_HDR_SIZE); + Q_ASSERT(msgLen == PB_HDR_SIZE); + + type = qFromBigEndian(&msg[0]); + method = qFromBigEndian(&msg[2]); + len = qFromBigEndian(&msg[4]); + //qDebug("type = %d, method = %d, len = %d", type, method, len); + + if (type != PB_MSG_TYPE_REQUEST) + { + qDebug("server(%s): unexpected msg type %d (expected %d)", __FUNCTION__, + type, PB_MSG_TYPE_REQUEST); + error = QString("unexpected msg type %1; expected %2") + .arg(type).arg(PB_MSG_TYPE_REQUEST); + goto _error_exit; + } + + // If RPC is not checkVersion, ensure compat check is already done + if (!isCompatCheckDone && method != 15) { + qDebug("server(%s): version compatibility check pending", + __FUNCTION__); + error = "version compatibility check pending"; + disconnect = true; + goto _error_exit; + } + + if (method >= service->GetDescriptor()->method_count()) + { + qDebug("server(%s): invalid method id %d", __FUNCTION__, method); + error = QString("invalid RPC method %1").arg(method); + goto _error_exit; + } + + methodDesc = service->GetDescriptor()->method(method); + if (!methodDesc) + { + qDebug("server(%s): invalid method id %d", __FUNCTION__, method); + error = QString("invalid RPC method %1").arg(method); + goto _error_exit; + } + + if (isPending) + { + qDebug("server(%s): rpc pending, try again", __FUNCTION__); + error = QString("RPC %1() is pending; only one RPC allowed at a time; " + "try again!").arg(QString::fromStdString( + service->GetDescriptor()->method( + pendingMethodId)->name())); + goto _error_exit; + } + + pendingMethodId = method; + isPending = true; + + req = service->GetRequestPrototype(methodDesc).New(); + resp = service->GetResponsePrototype(methodDesc).New(); + + if (len) { + bool ok = req->ParseFromBoundedZeroCopyStream(inStream, len); + if (!ok) + qWarning("ParseFromBoundedZeroCopyStream fail " + "for method %d and len %d", method, len); + } + + if (!req->IsInitialized()) + { + qWarning("Missing required fields in request <----"); + qDebug("method = %d\n" + "req = \n%s" + "missing = \n%s----->", + method, req->DebugString().c_str(), + req->InitializationErrorString().c_str()); + error = QString("RPC %1() missing required fields in request - %2") + .arg(QString::fromStdString( + service->GetDescriptor()->method( + pendingMethodId)->name()), + QString(req->InitializationErrorString().c_str())); + delete req; + delete resp; + + goto _error_exit2; + } + + if (method != 13) { + qDebug("Server(%s): successfully received/parsed msg <----", __FUNCTION__); + qDebug("method = %d\n" + "req = \n%s---->", + method, + req->DebugString().c_str()); + } + + controller = new PbRpcController(req, resp); + + //qDebug("before service->callmethod()"); + + service->CallMethod(methodDesc, controller, req, resp, + google::protobuf::NewCallback(this, &RpcConnection::sendRpcReply, + controller)); + + return; + +_error_exit: + inStream->Skip(len); +_error_exit2: + qDebug("server(%s): return error %s for msg from client", __FUNCTION__, + qPrintable(error)); + pendingMethodId = method; + isPending = true; + controller = new PbRpcController(NULL, NULL); + controller->SetFailed(error); + if (disconnect) + controller->TriggerDisconnect(); + sendRpcReply(controller); + return; +} + +void RpcConnection::connIdMsgHandler(QtMsgType /*type*/, const char* msg) +{ + if (connId.hasLocalData()) { + QString newMsg(*connId.localData()); + newMsg.append(msg); + newMsg.replace(QChar('\n'), QString("\n").append(*connId.localData())); + fprintf(stderr, "%s\n", qPrintable(newMsg)); + fflush(stderr); + return; + } + + fprintf(stderr, "%s\n", msg); + fflush(stderr); +} diff --git a/rpc/rpcconn.h b/rpc/rpcconn.h new file mode 100644 index 0000000..e41d8e2 --- /dev/null +++ b/rpc/rpcconn.h @@ -0,0 +1,75 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _RPC_CONNECTION_H +#define _RPC_CONNECTION_H + +#include + +// forward declarations +class PbRpcController; +class QTcpSocket; +namespace google { + namespace protobuf { + class Service; + namespace io { + class CopyingInputStreamAdaptor; + class CopyingOutputStreamAdaptor; + } + } +} + +class RpcConnection : public QObject +{ + Q_OBJECT + +public: + RpcConnection(int socketDescriptor, ::google::protobuf::Service *service); + virtual ~RpcConnection(); + static void connIdMsgHandler(QtMsgType type, const char* msg); + +private: + void writeHeader(char* header, quint16 type, quint16 method, + quint32 length); + void sendRpcReply(PbRpcController *controller); + +signals: + void closed(); + +private slots: + void start(); + void on_clientSock_dataAvail(); + void on_clientSock_error(QAbstractSocket::SocketError socketError); + void on_clientSock_disconnected(); + +private: + int socketDescriptor; + QTcpSocket *clientSock; + + ::google::protobuf::Service *service; + ::google::protobuf::io::CopyingInputStreamAdaptor *inStream; + ::google::protobuf::io::CopyingOutputStreamAdaptor *outStream; + + bool isPending; + int pendingMethodId; + + bool isCompatCheckDone; +}; + +#endif diff --git a/rpc/rpcserver.cpp b/rpc/rpcserver.cpp new file mode 100644 index 0000000..dad4b76 --- /dev/null +++ b/rpc/rpcserver.cpp @@ -0,0 +1,80 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "rpcserver.h" + +#include "rpcconn.h" + +#include + +// FIXME: QThreadX till we change minimum version of Qt from Qt4.3+ to Qt4.4+ +class QThreadX: public QThread +{ +protected: + virtual ~QThreadX() { qDebug("QThreadX going down!"); } + void run() { exec(); } +}; + +RpcServer::RpcServer() +{ + service = NULL; + + qInstallMsgHandler(RpcConnection::connIdMsgHandler); +} + +RpcServer::~RpcServer() +{ +} + +bool RpcServer::registerService(::google::protobuf::Service *service, + quint16 tcpPortNum) +{ + this->service = service; + + if (!listen(QHostAddress::Any, tcpPortNum)) + { + qDebug("Unable to start the server: %s", + errorString().toAscii().constData()); + return false; + } + + qDebug("The server is running on %s: %d", + serverAddress().toString().toAscii().constData(), + serverPort()); + return true; +} + +void RpcServer::incomingConnection(int socketDescriptor) +{ + QThread *thread = new QThreadX; // FIXME:QThreadX pending Qt4.4+ + RpcConnection *conn = new RpcConnection(socketDescriptor, service); + + conn->moveToThread(thread); + + connect(thread, SIGNAL(started()), conn, SLOT(start())); + + // NOTE: conn "self-destructs" after emitting closed + // use 'closed' to stop execution of the thread + connect(conn, SIGNAL(closed()), thread, SLOT(quit())); + + // setup thread to "self-destruct" when it is done + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + thread->start(); +} diff --git a/rpc/rpcserver.h b/rpc/rpcserver.h new file mode 100644 index 0000000..0a4acdd --- /dev/null +++ b/rpc/rpcserver.h @@ -0,0 +1,50 @@ +/* +Copyright (C) 2010, 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _RPC_SERVER_H +#define _RPC_SERVER_H + +#include + +// forward declaration +namespace google { + namespace protobuf { + class Service; + } +} + +class RpcServer : public QTcpServer +{ + Q_OBJECT + +public: + RpcServer(); //! \todo (LOW) use 'parent' param + virtual ~RpcServer(); + + bool registerService(::google::protobuf::Service *service, + quint16 tcpPortNum); + +protected: + void incomingConnection(int socketDescriptor); + +private: + ::google::protobuf::Service *service; +}; + +#endif diff --git a/server/abstractport.cpp b/server/abstractport.cpp new file mode 100644 index 0000000..cda4a35 --- /dev/null +++ b/server/abstractport.cpp @@ -0,0 +1,599 @@ +/* +Copyright (C) 2010-2012 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#define __STDC_FORMAT_MACROS + +#include "abstractport.h" + +#include "../common/streambase.h" +#include "../common/abstractprotocol.h" + +#include +#include + +#include +#include +#include + +AbstractPort::AbstractPort(int id, const char *device) +{ + isUsable_ = true; + data_.mutable_port_id()->set_id(id); + data_.set_name(device); + + //! \todo (LOW) admin enable/disable of port + data_.set_is_enabled(true); + + data_.set_is_exclusive_control(false); + + isSendQueueDirty_ = false; + linkState_ = OstProto::LinkStateUnknown; + minPacketSetSize_ = 1; + + maxStatsValue_ = ULLONG_MAX; // assume 64-bit stats + memset((void*) &stats_, 0, sizeof(stats_)); + resetStats(); +} + +AbstractPort::~AbstractPort() +{ +} + +void AbstractPort::init() +{ +} + +bool AbstractPort::modify(const OstProto::Port &port) +{ + bool ret = false; + + //! \todo Use reflection to find out which fields are set + if (port.has_is_exclusive_control()) + { + bool val = port.is_exclusive_control(); + + ret = setExclusiveControl(val); + if (ret) + data_.set_is_exclusive_control(val); + } + + if (port.has_transmit_mode()) + data_.set_transmit_mode(port.transmit_mode()); + + if (port.has_user_name()) { + data_.set_user_name(port.user_name()); + // TODO: notify other users + } + + return ret; +} + +StreamBase* AbstractPort::streamAtIndex(int index) +{ + Q_ASSERT(index < streamList_.size()); + return streamList_.at(index); +} + +StreamBase* AbstractPort::stream(int streamId) +{ + for (int i = 0; i < streamList_.size(); i++) + { + if ((uint)streamId == streamList_.at(i)->id()) + return streamList_.at(i); + } + + return NULL; +} + +bool AbstractPort::addStream(StreamBase *stream) +{ + streamList_.append(stream); + isSendQueueDirty_ = true; + return true; +} + +bool AbstractPort::deleteStream(int streamId) +{ + for (int i = 0; i < streamList_.size(); i++) + { + StreamBase *stream; + + if ((uint)streamId == streamList_.at(i)->id()) + { + stream = streamList_.takeAt(i); + delete stream; + + isSendQueueDirty_ = true; + return true; + } + } + + return false; +} + +void AbstractPort::addNote(QString note) +{ + QString notes = QString::fromStdString(data_.notes()); + + note.prepend("
  • "); + note.append("
  • "); + + if (notes.isEmpty()) + notes="Limitation(s)
      "; + else + notes.remove("
    "); + + notes.append(note); + notes.append(""); + + data_.set_notes(notes.toStdString()); +} + +void AbstractPort::updatePacketList() +{ + switch(data_.transmit_mode()) + { + case OstProto::kSequentialTransmit: + updatePacketListSequential(); + break; + case OstProto::kInterleavedTransmit: + updatePacketListInterleaved(); + break; + default: + Q_ASSERT(false); // Unreachable!!! + break; + } +} + +void AbstractPort::updatePacketListSequential() +{ + long sec = 0; + long nsec = 0; + + qDebug("In %s", __FUNCTION__); + + // First sort the streams by ordinalValue + qSort(streamList_.begin(), streamList_.end(), StreamBase::StreamLessThan); + + clearPacketList(); + + for (int i = 0; i < streamList_.size(); i++) + { + if (streamList_[i]->isEnabled()) + { + int len = 0; + ulong n, x, y; + ulong burstSize; + double ibg = 0; + quint64 ibg1 = 0, ibg2 = 0; + quint64 nb1 = 0, nb2 = 0; + double ipg = 0; + quint64 ipg1 = 0, ipg2 = 0; + quint64 npx1 = 0, npx2 = 0; + quint64 npy1 = 0, npy2 = 0; + quint64 loopDelay; + ulong frameVariableCount = streamList_[i]->frameVariableCount(); + + // We derive n, x, y such that + // n * x + y = total number of packets to be sent + + switch (streamList_[i]->sendUnit()) + { + case OstProto::StreamControl::e_su_bursts: + burstSize = streamList_[i]->burstSize(); + x = AbstractProtocol::lcm(frameVariableCount, burstSize); + n = ulong(burstSize * streamList_[i]->burstRate() + * streamList_[i]->numBursts()) / x; + y = ulong(burstSize * streamList_[i]->burstRate() + * streamList_[i]->numBursts()) % x; + if (streamList_[i]->burstRate() > 0) + { + ibg = 1e9/double(streamList_[i]->burstRate()); + ibg1 = quint64(ceil(ibg)); + ibg2 = quint64(floor(ibg)); + nb1 = quint64((ibg - double(ibg2)) * double(x)); + nb2 = x - nb1; + } + loopDelay = ibg2; + break; + case OstProto::StreamControl::e_su_packets: + x = frameVariableCount; + n = 2; + while (x < minPacketSetSize_) + x = frameVariableCount*n++; + n = streamList_[i]->numPackets() / x; + y = streamList_[i]->numPackets() % x; + burstSize = x + y; + if (streamList_[i]->packetRate() > 0) + { + ipg = 1e9/double(streamList_[i]->packetRate()); + ipg1 = quint64(ceil(ipg)); + ipg2 = quint64(floor(ipg)); + npx1 = quint64((ipg - double(ipg2)) * double(x)); + npx2 = x - npx1; + npy1 = quint64((ipg - double(ipg2)) * double(y)); + npy2 = y - npy1; + } + loopDelay = ipg2; + break; + default: + qWarning("Unhandled stream control unit %d", + streamList_[i]->sendUnit()); + continue; + } + + qDebug("\nframeVariableCount = %lu", frameVariableCount); + qDebug("n = %lu, x = %lu, y = %lu, burstSize = %lu", + n, x, y, burstSize); + + qDebug("ibg = %g", ibg); + qDebug("ibg1 = %" PRIu64, ibg1); + qDebug("nb1 = %" PRIu64, nb1); + qDebug("ibg2 = %" PRIu64, ibg2); + qDebug("nb2 = %" PRIu64 "\n", nb2); + + qDebug("ipg = %g", ipg); + qDebug("ipg1 = %" PRIu64, ipg1); + qDebug("npx1 = %" PRIu64, npx1); + qDebug("npy1 = %" PRIu64, npy1); + qDebug("ipg2 = %" PRIu64, ipg2); + qDebug("npx2 = %" PRIu64, npx2); + qDebug("npy2 = %" PRIu64 "\n", npy2); + + if (n > 1) + loopNextPacketSet(x, n, 0, loopDelay); + else if (n == 0) + x = 0; + + for (uint j = 0; j < (x+y); j++) + { + + if (j == 0 || frameVariableCount > 1) + { + len = streamList_[i]->frameValue( + pktBuf_, sizeof(pktBuf_), j); + } + if (len <= 0) + continue; + + qDebug("q(%d, %d) sec = %lu nsec = %lu", + i, j, sec, nsec); + + appendToPacketList(sec, nsec, pktBuf_, len); + + if ((j > 0) && (((j+1) % burstSize) == 0)) + { + nsec += (j < nb1) ? ibg1 : ibg2; + while (nsec >= long(1e9)) + { + sec++; + nsec -= long(1e9); + } + } + else + { + if (j < x) + nsec += (j < npx1) ? ipg1 : ipg2; + else + nsec += ((j-x) < npy1) ? ipg1 : ipg2; + + while (nsec >= long(1e9)) + { + sec++; + nsec -= long(1e9); + } + } + } + + switch(streamList_[i]->nextWhat()) + { + case ::OstProto::StreamControl::e_nw_stop: + goto _stop_no_more_pkts; + + case ::OstProto::StreamControl::e_nw_goto_id: + /*! \todo (MED): define and use + streamList_[i].d.control().goto_stream_id(); */ + + /*! \todo (MED): assumes goto Id is less than current!!!! + To support goto to any id, do + if goto_id > curr_id then + i = goto_id; + goto restart; + else + returnToQIdx = 0; + */ + + setPacketListLoopMode(true, 0, + streamList_[i]->sendUnit() == + StreamBase::e_su_bursts ? ibg1 : ipg1); + goto _stop_no_more_pkts; + + case ::OstProto::StreamControl::e_nw_goto_next: + break; + + default: + qFatal("---------- %s: Unhandled case (%d) -----------", + __FUNCTION__, streamList_[i]->nextWhat() ); + break; + } + + } // if (stream is enabled) + } // for (numStreams) + +_stop_no_more_pkts: + isSendQueueDirty_ = false; +} + +void AbstractPort::updatePacketListInterleaved() +{ + int numStreams = 0; + quint64 minGap = ULLONG_MAX; + quint64 duration = quint64(1e9); + QList ibg1, ibg2; + QList nb1, nb2; + QList ipg1, ipg2; + QList np1, np2; + QList schedSec, schedNsec; + QList pktCount, burstCount; + QList burstSize; + QList isVariable; + QList pktBuf; + QList pktLen; + + qDebug("In %s", __FUNCTION__); + + // First sort the streams by ordinalValue + qSort(streamList_.begin(), streamList_.end(), StreamBase::StreamLessThan); + + clearPacketList(); + + for (int i = 0; i < streamList_.size(); i++) + { + if (!streamList_[i]->isEnabled()) + continue; + + double numBursts = 0; + double numPackets = 0; + + quint64 _burstSize = 0; + double ibg = 0; + quint64 _ibg1 = 0, _ibg2 = 0; + quint64 _nb1 = 0, _nb2 = 0; + double ipg = 0; + quint64 _ipg1 = 0, _ipg2 = 0; + quint64 _np1 = 0, _np2 = 0; + + switch (streamList_[i]->sendUnit()) + { + case OstProto::StreamControl::e_su_bursts: + numBursts = streamList_[i]->burstRate(); + if (streamList_[i]->burstRate() > 0) + { + ibg = 1e9/double(streamList_[i]->burstRate()); + _ibg1 = quint64(ceil(ibg)); + _ibg2 = quint64(floor(ibg)); + _nb1 = quint64((ibg - double(_ibg2)) * double(numBursts)); + _nb2 = quint64(numBursts) - _nb1; + _burstSize = streamList_[i]->burstSize(); + } + break; + case OstProto::StreamControl::e_su_packets: + numPackets = streamList_[i]->packetRate(); + if (streamList_[i]->packetRate() > 0) + { + ipg = 1e9/double(streamList_[i]->packetRate()); + _ipg1 = llrint(ceil(ipg)); + _ipg2 = quint64(floor(ipg)); + _np1 = quint64((ipg - double(_ipg2)) * double(numPackets)); + _np2 = quint64(numPackets) - _np1; + _burstSize = 1; + } + break; + default: + qWarning("Unhandled stream control unit %d", + streamList_[i]->sendUnit()); + continue; + } + qDebug("numBursts = %g, numPackets = %g\n", numBursts, numPackets); + + qDebug("ibg = %g", ibg); + qDebug("ibg1 = %" PRIu64, _ibg1); + qDebug("nb1 = %" PRIu64, _nb1); + qDebug("ibg2 = %" PRIu64, _ibg2); + qDebug("nb2 = %" PRIu64 "\n", _nb2); + + qDebug("ipg = %g", ipg); + qDebug("ipg1 = %" PRIu64, _ipg1); + qDebug("np1 = %" PRIu64, _np1); + qDebug("ipg2 = %" PRIu64, _ipg2); + qDebug("np2 = %" PRIu64 "\n", _np2); + + + if (_ibg2 && (_ibg2 < minGap)) + minGap = _ibg2; + + if (_ibg1 && (_ibg1 > duration)) + duration = _ibg1; + + ibg1.append(_ibg1); + ibg2.append(_ibg2); + + nb1.append(_nb1); + nb2.append(_nb1); + + burstSize.append(_burstSize); + + if (_ipg2 && (_ipg2 < minGap)) + minGap = _ipg2; + + if (_np1) + { + if (_ipg1 && (_ipg1 > duration)) + duration = _ipg1; + } + else + { + if (_ipg2 && (_ipg2 > duration)) + duration = _ipg2; + } + + ipg1.append(_ipg1); + ipg2.append(_ipg2); + + np1.append(_np1); + np2.append(_np1); + + schedSec.append(0); + schedNsec.append(0); + + pktCount.append(0); + burstCount.append(0); + + if (streamList_[i]->isFrameVariable()) + { + isVariable.append(true); + pktBuf.append(QByteArray()); + pktLen.append(0); + } + else + { + isVariable.append(false); + pktBuf.append(QByteArray()); + pktBuf.last().resize(kMaxPktSize); + pktLen.append(streamList_[i]->frameValue( + (uchar*)pktBuf.last().data(), pktBuf.last().size(), 0)); + } + + numStreams++; + } // for i + + qDebug("minGap = %" PRIu64, minGap); + qDebug("duration = %" PRIu64, duration); + + uchar* buf; + int len; + quint64 durSec = duration/ulong(1e9); + quint64 durNsec = duration % ulong(1e9); + quint64 sec = 0; + quint64 nsec = 0; + quint64 lastPktTxSec = 0; + quint64 lastPktTxNsec = 0; + do + { + for (int i = 0; i < numStreams; i++) + { + // If a packet is not scheduled yet, look at the next stream + if ((schedSec.at(i) > sec) || (schedNsec.at(i) > nsec)) + continue; + + for (uint j = 0; j < burstSize[i]; j++) + { + if (isVariable.at(i)) + { + buf = pktBuf_; + len = streamList_[i]->frameValue(pktBuf_, sizeof(pktBuf_), + pktCount[i]); + } + else + { + buf = (uchar*) pktBuf.at(i).data(); + len = pktLen.at(i); + } + + if (len <= 0) + continue; + + qDebug("q(%d) sec = %" PRIu64 " nsec = %" PRIu64, i, sec, nsec); + appendToPacketList(sec, nsec, buf, len); + lastPktTxSec = sec; + lastPktTxNsec = nsec; + + pktCount[i]++; + schedNsec[i] += (pktCount.at(i) < np1.at(i)) ? + ipg1.at(i) : ipg2.at(i); + while (schedNsec.at(i) >= 1e9) + { + schedSec[i]++; + schedNsec[i] -= long(1e9); + } + } + + burstCount[i]++; + schedNsec[i] += (burstCount.at(i) < nb1.at(i)) ? + ibg1.at(i) : ibg2.at(i); + while (schedNsec.at(i) >= 1e9) + { + schedSec[i]++; + schedNsec[i] -= long(1e9); + } + } + + nsec += minGap; + while (nsec >= 1e9) + { + sec++; + nsec -= long(1e9); + } + } while ((sec < durSec) || (nsec < durNsec)); + + qint64 delaySec = durSec - lastPktTxSec; + qint64 delayNsec = durNsec - lastPktTxNsec; + while (delayNsec < 0) + { + delayNsec += long(1e9); + delaySec--; + } + qDebug("loop Delay = %" PRId64 "/%" PRId64, delaySec, delayNsec); + setPacketListLoopMode(true, delaySec, delayNsec); + isSendQueueDirty_ = false; +} + +void AbstractPort::stats(PortStats *stats) +{ + stats->rxPkts = (stats_.rxPkts >= epochStats_.rxPkts) ? + stats_.rxPkts - epochStats_.rxPkts : + stats_.rxPkts + (maxStatsValue_ - epochStats_.rxPkts); + stats->rxBytes = (stats_.rxBytes >= epochStats_.rxBytes) ? + stats_.rxBytes - epochStats_.rxBytes : + stats_.rxBytes + (maxStatsValue_ - epochStats_.rxBytes); + stats->rxPps = stats_.rxPps; + stats->rxBps = stats_.rxBps; + + stats->txPkts = (stats_.txPkts >= epochStats_.txPkts) ? + stats_.txPkts - epochStats_.txPkts : + stats_.txPkts + (maxStatsValue_ - epochStats_.txPkts); + stats->txBytes = (stats_.txBytes >= epochStats_.txBytes) ? + stats_.txBytes - epochStats_.txBytes : + stats_.txBytes + (maxStatsValue_ - epochStats_.txBytes); + stats->txPps = stats_.txPps; + stats->txBps = stats_.txBps; + + stats->rxDrops = (stats_.rxDrops >= epochStats_.rxDrops) ? + stats_.rxDrops - epochStats_.rxDrops : + stats_.rxDrops + (maxStatsValue_ - epochStats_.rxDrops); + stats->rxErrors = (stats_.rxErrors >= epochStats_.rxErrors) ? + stats_.rxErrors - epochStats_.rxErrors : + stats_.rxErrors + (maxStatsValue_ - epochStats_.rxErrors); + stats->rxFifoErrors = (stats_.rxFifoErrors >= epochStats_.rxFifoErrors) ? + stats_.rxFifoErrors - epochStats_.rxFifoErrors : + stats_.rxFifoErrors + (maxStatsValue_ - epochStats_.rxFifoErrors); + stats->rxFrameErrors = (stats_.rxFrameErrors >= epochStats_.rxFrameErrors) ? + stats_.rxFrameErrors - epochStats_.rxFrameErrors : + stats_.rxFrameErrors + (maxStatsValue_ - epochStats_.rxFrameErrors); +} diff --git a/server/abstractport.h b/server/abstractport.h new file mode 100644 index 0000000..44f0c1d --- /dev/null +++ b/server/abstractport.h @@ -0,0 +1,127 @@ +/* +Copyright (C) 2010-2012 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SERVER_ABSTRACT_PORT_H +#define _SERVER_ABSTRACT_PORT_H + +#include +#include + +#include "../common/protocol.pb.h" + +class StreamBase; +class QIODevice; + +class AbstractPort +{ +public: + struct PortStats + { + quint64 rxPkts; + quint64 rxBytes; + quint64 rxPps; + quint64 rxBps; + + quint64 rxDrops; + quint64 rxErrors; + quint64 rxFifoErrors; + quint64 rxFrameErrors; + + quint64 txPkts; + quint64 txBytes; + quint64 txPps; + quint64 txBps; + }; + + AbstractPort(int id, const char *device); + virtual ~AbstractPort(); + + bool isUsable() { return isUsable_; } + + virtual void init(); + + int id() { return data_.port_id().id(); } + const char* name() { return data_.name().c_str(); } + void protoDataCopyInto(OstProto::Port *port) { port->CopyFrom(data_); } + + bool modify(const OstProto::Port &port); + + virtual OstProto::LinkState linkState() { return linkState_; } + virtual bool hasExclusiveControl() = 0; + virtual bool setExclusiveControl(bool exclusive) = 0; + + int streamCount() { return streamList_.size(); } + StreamBase* streamAtIndex(int index); + StreamBase* stream(int streamId); + bool addStream(StreamBase *stream); + bool deleteStream(int streamId); + + bool isDirty() { return isSendQueueDirty_; } + void setDirty() { isSendQueueDirty_ = true; } + + virtual void clearPacketList() = 0; + virtual void loopNextPacketSet(qint64 size, qint64 repeats, + long repeatDelaySec, long repeatDelayNsec) = 0; + virtual bool appendToPacketList(long sec, long nsec, const uchar *packet, + int length) = 0; + virtual void setPacketListLoopMode(bool loop, + quint64 secDelay, quint64 nsecDelay) = 0; + void updatePacketList(); + + virtual void startTransmit() = 0; + virtual void stopTransmit() = 0; + virtual bool isTransmitOn() = 0; + + virtual void startCapture() = 0; + virtual void stopCapture() = 0; + virtual bool isCaptureOn() = 0; + virtual QIODevice* captureData() = 0; + + void stats(PortStats *stats); + void resetStats() { epochStats_ = stats_; } + +protected: + void addNote(QString note); + + void updatePacketListSequential(); + void updatePacketListInterleaved(); + + bool isUsable_; + OstProto::Port data_; + OstProto::LinkState linkState_; + ulong minPacketSetSize_; + + quint64 maxStatsValue_; + struct PortStats stats_; + //! \todo Need lock for stats access/update + +private: + bool isSendQueueDirty_; + + static const int kMaxPktSize = 16384; + uchar pktBuf_[kMaxPktSize]; + + /*! \note StreamBase::id() and index into streamList[] are NOT same! */ + QList streamList_; + + struct PortStats epochStats_; + +}; + +#endif diff --git a/server/bsdport.cpp b/server/bsdport.cpp new file mode 100644 index 0000000..4ac9ab7 --- /dev/null +++ b/server/bsdport.cpp @@ -0,0 +1,355 @@ +/* +Copyright (C) 2012 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "bsdport.h" + +#ifdef Q_OS_BSD4 + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_MAC +#define ifr_flagshigh ifr_flags +#define IFF_PPROMISC (IFF_PROMISC << 16) +#endif + +QList BsdPort::allPorts_; +BsdPort::StatsMonitor *BsdPort::monitor_; + +const quint32 kMaxValue32 = 0xffffffff; + +BsdPort::BsdPort(int id, const char *device) + : PcapPort(id, device) +{ + isPromisc_ = true; + clearPromisc_ = false; + + // We don't need per port Rx/Tx monitors for Bsd + delete monitorRx_; + delete monitorTx_; + monitorRx_ = monitorTx_ = NULL; + + // We have one monitor for both Rx/Tx of all ports + if (!monitor_) + monitor_ = new StatsMonitor(); + + data_.set_is_exclusive_control(hasExclusiveControl()); + minPacketSetSize_ = 16; + + qDebug("adding dev to all ports list <%s>", device); + allPorts_.append(this); + + maxStatsValue_ = ULONG_MAX; +} + +BsdPort::~BsdPort() +{ + qDebug("In %s", __FUNCTION__); + + if (monitor_->isRunning()) + { + monitor_->stop(); + monitor_->wait(); + } + + if (clearPromisc_) + { + int sd = socket(AF_INET, SOCK_DGRAM, 0); + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name(), sizeof(ifr.ifr_name)); + + if (ioctl(sd, SIOCGIFFLAGS, &ifr) != -1) + { + short promisc = IFF_PPROMISC >> 16; + + if (ifr.ifr_flagshigh & promisc) + { + ifr.ifr_flagshigh &= ~promisc; + if (ioctl(sd, SIOCSIFFLAGS, &ifr) == -1) + qDebug("Failed clearing promisc flag. SIOCSIFFLAGS failed: %s", + strerror(errno)); + else + qDebug("Cleared promisc successfully"); + } + else + qDebug("clear_promisc is set but IFF_PPROMISC is not?"); + } + else + qDebug("Failed clearing promisc flag. SIOCGIFFLAGS failed: %s", + strerror(errno)); + + close(sd); + } +} + +void BsdPort::init() +{ + if (!monitor_->isRunning()) + monitor_->start(); + + monitor_->waitForSetupFinished(); + + if (!isPromisc_) + addNote("Non Promiscuous Mode"); +} + +bool BsdPort::hasExclusiveControl() +{ + // TODO + return false; +} + +bool BsdPort::setExclusiveControl(bool /*exclusive*/) +{ + // TODO + return false; +} + +BsdPort::StatsMonitor::StatsMonitor() + : QThread() +{ + stop_ = false; + setupDone_ = false; +} + +void BsdPort::StatsMonitor::run() +{ + int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0}; + const int mibLen = sizeof(mib)/sizeof(mib[0]); + QHash portStats; + QHash linkState; + int sd; + QByteArray buf; + size_t len; + char *p, *end; + int count; + struct ifreq ifr; + + // + // We first setup stuff before we start polling for stats + // + if (sysctl(mib, mibLen, NULL, &len, NULL, 0) < 0) + { + qWarning("sysctl NET_RT_IFLIST(1) failed (%s)\n", strerror(errno)); + return; + } + + qDebug("sysctl mib returns reqd len = %d\n", (int) len); + len *= 2; // for extra room, just in case! + buf.fill('\0', len); + if (sysctl(mib, mibLen, buf.data(), &len, NULL, 0) < 0) + { + qWarning("sysctl NET_RT_IFLIST(2) failed(%s)\n", strerror(errno)); + return; + } + + sd = socket(AF_INET, SOCK_DGRAM, 0); + Q_ASSERT(sd >= 0); + memset(&ifr, 0, sizeof(ifr)); + + // + // Populate the port stats hash table + // + p = buf.data(); + end = p + len; + count = 0; + while (p < end) + { + struct if_msghdr *ifm = (struct if_msghdr*) p; + struct sockaddr_dl *sdl = (struct sockaddr_dl*) (ifm + 1); + + if (ifm->ifm_type == RTM_IFINFO) + { + char ifname[1024]; + + strncpy(ifname, sdl->sdl_data, sdl->sdl_nlen); + ifname[sdl->sdl_nlen] = 0; + + qDebug("if: %s(%d, %d)", ifname, ifm->ifm_index, sdl->sdl_index); + foreach(BsdPort* port, allPorts_) + { + if (strncmp(port->name(), sdl->sdl_data, sdl->sdl_nlen) == 0) + { + Q_ASSERT(ifm->ifm_index == sdl->sdl_index); + portStats[uint(ifm->ifm_index)] = &(port->stats_); + linkState[uint(ifm->ifm_index)] = &(port->linkState_); + + // Set promisc mode, if not already set + strncpy(ifr.ifr_name, port->name(), sizeof(ifr.ifr_name)); + if (ioctl(sd, SIOCGIFFLAGS, &ifr) != -1) + { + short promisc = IFF_PPROMISC >> 16; + + if ((ifr.ifr_flagshigh & promisc) == 0) + { + ifr.ifr_flagshigh |= promisc; + if (ioctl(sd, SIOCSIFFLAGS, &ifr) != -1) + { + qDebug("%s: set promisc successful", + port->name()); + port->clearPromisc_ = true; + } + else + { + port->isPromisc_ = false; + qDebug("%s: failed to set promisc; " + "SIOCSIFFLAGS failed (%s)", + port->name(), strerror(errno)); + } + } + else + qDebug("%s: promisc already set", port->name()); + } + else + { + port->isPromisc_ = false; + qDebug("%s: failed to set promisc; SIOCGIFFLAGS failed (%s)", + port->name(), strerror(errno)); + } + break; + } + } + count++; + } + p += ifm->ifm_msglen; + } + + qDebug("port count = %d\n", count); + if (count <= 0) + { + qWarning("no ports in NET_RT_IFLIST - no stats will be available"); + return; + } + + close(sd); + + qDebug("stats for %d ports setup", count); + setupDone_ = true; + + // + // We are all set - Let's start polling for stats! + // + while (!stop_) + { + if (sysctl(mib, mibLen, buf.data(), &len, NULL, 0) < 0) + { + qWarning("sysctl NET_RT_IFLIST(3) failed(%s)\n", strerror(errno)); + goto _try_later; + } + + p = buf.data(); + end = p + len; + + while (p < end) + { + struct if_msghdr *ifm = (struct if_msghdr*) p; + AbstractPort::PortStats *stats; + + if (ifm->ifm_type != RTM_IFINFO) + goto _next; + + stats = portStats[ifm->ifm_index]; + if (stats) + { + struct if_data *ifd = &(ifm->ifm_data); + OstProto::LinkState *state = linkState[ifm->ifm_index]; + u_long in_packets; + + Q_ASSERT(state); +#ifdef Q_OS_MAC + *state = ifm->ifm_flags & IFF_RUNNING ? + OstProto::LinkStateUp : OstProto::LinkStateDown; +#else + *state = (OstProto::LinkState) ifd->ifi_link_state; +#endif + + in_packets = ifd->ifi_ipackets + ifd->ifi_noproto; + stats->rxPps = + ((in_packets >= stats->rxPkts) ? + in_packets - stats->rxPkts : + in_packets + (kMaxValue32 - stats->rxPkts)) + / kRefreshFreq_; + stats->rxBps = + ((ifd->ifi_ibytes >= stats->rxBytes) ? + ifd->ifi_ibytes - stats->rxBytes : + ifd->ifi_ibytes + (kMaxValue32 - stats->rxBytes)) + / kRefreshFreq_; + stats->rxPkts = in_packets; + stats->rxBytes = ifd->ifi_ibytes; + stats->txPps = + ((ifd->ifi_opackets >= stats->txPkts) ? + ifd->ifi_opackets - stats->txPkts : + ifd->ifi_opackets + (kMaxValue32 - stats->txPkts)) + / kRefreshFreq_; + stats->txBps = + ((ifd->ifi_obytes >= stats->txBytes) ? + ifd->ifi_obytes - stats->txBytes : + ifd->ifi_obytes + (kMaxValue32 - stats->txBytes)) + / kRefreshFreq_; + stats->txPkts = ifd->ifi_opackets; + stats->txBytes = ifd->ifi_obytes; + + stats->rxDrops = ifd->ifi_iqdrops; + stats->rxErrors = ifd->ifi_ierrors; + } +_next: + p += ifm->ifm_msglen; + } +_try_later: + QThread::sleep(kRefreshFreq_); + } + + portStats.clear(); + linkState.clear(); +} + +void BsdPort::StatsMonitor::stop() +{ + stop_ = true; +} + +bool BsdPort::StatsMonitor::waitForSetupFinished(int msecs) +{ + QTime t; + + t.start(); + while (!setupDone_) + { + if (t.elapsed() > msecs) + return false; + + QThread::msleep(10); + } + + return true; +} +#endif diff --git a/server/bsdport.h b/server/bsdport.h new file mode 100644 index 0000000..776c39a --- /dev/null +++ b/server/bsdport.h @@ -0,0 +1,61 @@ +/* +Copyright (C) 2012 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SERVER_BSD_PORT_H +#define _SERVER_BSD_PORT_H + +#include + +#ifdef Q_OS_BSD4 + +#include "pcapport.h" + +class BsdPort : public PcapPort +{ +public: + BsdPort(int id, const char *device); + ~BsdPort(); + + void init(); + + virtual bool hasExclusiveControl(); + virtual bool setExclusiveControl(bool exclusive); + +protected: + class StatsMonitor: public QThread + { + public: + StatsMonitor(); + void run(); + void stop(); + bool waitForSetupFinished(int msecs = 10000); + private: + static const int kRefreshFreq_ = 1; // in seconds + bool stop_; + bool setupDone_; + }; + + bool isPromisc_; + bool clearPromisc_; + static QList allPorts_; + static StatsMonitor *monitor_; // rx/tx stats for ALL ports +}; +#endif + +#endif diff --git a/server/drone.cpp b/server/drone.cpp new file mode 100644 index 0000000..c46f1df --- /dev/null +++ b/server/drone.cpp @@ -0,0 +1,53 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "drone.h" + +#include "rpcserver.h" +#include "myservice.h" + +extern int myport; +extern const char* version; +extern const char* revision; + +Drone::Drone(QObject *parent) + : QObject(parent) +{ + rpcServer = new RpcServer(); + service = new MyService(); +} + +Drone::~Drone() +{ + delete rpcServer; + delete service; +} + +bool Drone::init() +{ + Q_ASSERT(rpcServer); + + if (!rpcServer->registerService(service, myport ? myport : 7878)) + { + //qCritical(qPrintable(rpcServer->errorString())); + return false; + } + + return true; +} diff --git a/server/drone.h b/server/drone.h new file mode 100644 index 0000000..9474207 --- /dev/null +++ b/server/drone.h @@ -0,0 +1,40 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _DRONE_H +#define _DRONE_H + +#include + +class RpcServer; +namespace OstProto { class OstService; } + +class Drone : public QObject +{ + Q_OBJECT +public: + Drone(QObject *parent = 0); + ~Drone(); + bool init(); + +private: + RpcServer *rpcServer; + OstProto::OstService *service; +}; +#endif diff --git a/server/drone.pro b/server/drone.pro new file mode 100644 index 0000000..d22f138 --- /dev/null +++ b/server/drone.pro @@ -0,0 +1,49 @@ +TEMPLATE = app +CONFIG += qt ver_info +QT += network script +QT -= gui +DEFINES += HAVE_REMOTE WPCAP +linux*:system(grep -q IFLA_STATS64 /usr/include/linux/if_link.h): \ + DEFINES += HAVE_IFLA_STATS64 +INCLUDEPATH += "../rpc" +win32 { + CONFIG += console + LIBS += -lwpcap -lpacket + CONFIG(debug, debug|release) { + LIBS += -L"../common/debug" -lostproto + LIBS += -L"../rpc/debug" -lpbrpc + POST_TARGETDEPS += \ + "../common/debug/libostproto.a" \ + "../rpc/debug/libpbrpc.a" + } else { + LIBS += -L"../common/release" -lostproto + LIBS += -L"../rpc/release" -lpbrpc + POST_TARGETDEPS += \ + "../common/release/libostproto.a" \ + "../rpc/release/libpbrpc.a" + } +} else { + LIBS += -lpcap + LIBS += -L"../common" -lostproto + LIBS += -L"../rpc" -lpbrpc + POST_TARGETDEPS += "../common/libostproto.a" "../rpc/libpbrpc.a" +} +LIBS += -lm +LIBS += -lprotobuf +HEADERS += drone.h +SOURCES += \ + drone_main.cpp \ + drone.cpp \ + portmanager.cpp \ + abstractport.cpp \ + pcapport.cpp \ + bsdport.cpp \ + linuxport.cpp \ + winpcapport.cpp +SOURCES += myservice.cpp +SOURCES += pcapextra.cpp + +QMAKE_DISTCLEAN += object_script.* + +include (../install.pri) +include (../version.pri) diff --git a/server/drone_main.cpp b/server/drone_main.cpp new file mode 100644 index 0000000..e5c1abd --- /dev/null +++ b/server/drone_main.cpp @@ -0,0 +1,107 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "drone.h" + +#include "../common/protocolmanager.h" +#include "settings.h" + +#include + +#include +#include + +#ifdef Q_OS_UNIX +#include +#endif + +extern ProtocolManager *OstProtocolManager; +extern char *version; +extern char *revision; + +QSettings *appSettings; +int myport; + +void cleanup(int /*signum*/) +{ + QCoreApplication::instance()->exit(-1); +} + +int main(int argc, char *argv[]) +{ + int exitCode = 0; + QCoreApplication app(argc, argv); + Drone *drone; + + // TODO: command line options + // -v (--version) + // -h (--help) + // -p (--portnum) + if (argc > 1) + myport = atoi(argv[1]); + + app.setApplicationName("Drone"); + app.setOrganizationName("Ostinato"); + + /* (Portable Mode) If we have a .ini file in the same directory as the + executable, we use that instead of the platform specific location + and format for the settings */ + QString portableIni = QCoreApplication::applicationDirPath() + + "/drone.ini"; + if (QFile::exists(portableIni)) + appSettings = new QSettings(portableIni, QSettings::IniFormat); + else + appSettings = new QSettings(QSettings::IniFormat, + QSettings::UserScope, + app.organizationName(), + app.applicationName().toLower()); + + drone = new Drone(); + OstProtocolManager = new ProtocolManager(); + + if (!drone->init()) + { + exitCode = -1; + goto _exit; + } + + qDebug("Version: %s", version); + qDebug("Revision: %s", revision); + +#ifdef Q_OS_UNIX + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = cleanup; + if (sigaction(SIGTERM, &sa, NULL)) + qDebug("Failed to install SIGTERM handler. Cleanup may not happen!!!"); + if (sigaction(SIGINT, &sa, NULL)) + qDebug("Failed to install SIGINT handler. Cleanup may not happen!!!"); +#endif + + exitCode = app.exec(); + +_exit: + delete drone; + delete OstProtocolManager; + + google::protobuf::ShutdownProtobufLibrary(); + + return exitCode; +} + diff --git a/server/icons/portgroup.png b/server/icons/portgroup.png new file mode 100644 index 0000000..9bc37dc Binary files /dev/null and b/server/icons/portgroup.png differ diff --git a/server/linuxport.cpp b/server/linuxport.cpp new file mode 100644 index 0000000..55468ef --- /dev/null +++ b/server/linuxport.cpp @@ -0,0 +1,819 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "linuxport.h" + +#ifdef Q_OS_LINUX + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +QList LinuxPort::allPorts_; +LinuxPort::StatsMonitor *LinuxPort::monitor_; + +const quint32 kMaxValue32 = 0xffffffff; +const quint64 kMaxValue64 = 0xffffffffffffffffULL; + +#ifdef HAVE_IFLA_STATS64 +#define X_IFLA_STATS IFLA_STATS64 +typedef struct rtnl_link_stats64 x_rtnl_link_stats; +#else +#define X_IFLA_STATS IFLA_STATS +typedef struct rtnl_link_stats x_rtnl_link_stats; +#endif + +LinuxPort::LinuxPort(int id, const char *device) + : PcapPort(id, device) +{ + isPromisc_ = true; + clearPromisc_ = false; + + // We don't need per port Rx/Tx monitors for Linux + delete monitorRx_; + delete monitorTx_; + monitorRx_ = monitorTx_ = NULL; + + // We have one monitor for both Rx/Tx of all ports + if (!monitor_) + monitor_ = new StatsMonitor(); + + data_.set_is_exclusive_control(hasExclusiveControl()); + minPacketSetSize_ = 16; + + qDebug("adding dev to all ports list <%s>", device); + allPorts_.append(this); + + // A port can support either 32 or 64 bit stats - we will attempt + // to guess this for each port and initialize this variable at + // run time when the counter wraps around + maxStatsValue_ = 0; +} + +LinuxPort::~LinuxPort() +{ + qDebug("In %s", __FUNCTION__); + + if (monitor_->isRunning()) + { + monitor_->stop(); + monitor_->wait(); + } + + if (clearPromisc_) + { + int sd = socket(AF_INET, SOCK_DGRAM, 0); + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, name(), sizeof(ifr.ifr_name)); + + if (ioctl(sd, SIOCGIFFLAGS, &ifr) != -1) + { + if (ifr.ifr_flags & IFF_PROMISC) + { + ifr.ifr_flags &= ~IFF_PROMISC; + if (ioctl(sd, SIOCSIFFLAGS, &ifr) == -1) + qDebug("Failed clearing promisc flag. SIOCSIFFLAGS failed: %s", + strerror(errno)); + } + } + else + qDebug("Failed clearing promisc flag. SIOCGIFFLAGS failed: %s", + strerror(errno)); + + close(sd); + } +} + +void LinuxPort::init() +{ + if (!monitor_->isRunning()) + monitor_->start(); + + monitor_->waitForSetupFinished(); + + if (!isPromisc_) + addNote("Non Promiscuous Mode"); +} + +OstProto::LinkState LinuxPort::linkState() +{ + return linkState_; +} + +bool LinuxPort::hasExclusiveControl() +{ + // TODO + return false; +} + +bool LinuxPort::setExclusiveControl(bool /*exclusive*/) +{ + // TODO + return false; +} + +LinuxPort::StatsMonitor::StatsMonitor() + : QThread() +{ + stop_ = false; + setupDone_ = false; + ioctlSocket_ = socket(AF_INET, SOCK_DGRAM, 0); + Q_ASSERT(ioctlSocket_ >= 0); +} + +LinuxPort::StatsMonitor::~StatsMonitor() +{ + close(ioctlSocket_); +} + +void LinuxPort::StatsMonitor::run() +{ + if (netlinkStats() < 0) + { + qDebug("netlink stats not available - using /proc stats"); + procStats(); + } +} + +void LinuxPort::StatsMonitor::procStats() +{ + PortStats **portStats; + int fd; + QByteArray buf; + int len; + char *p, *end; + int count, index; + const char* fmtopt[] = { + "%llu%llu%llu%llu%llu%llu%u%u%llu%llu%u%u%u%u%u%u\n", + "%llu%llu%llu%llu%llu%llu%n%n%llu%llu%u%u%u%u%u%n\n", + }; + const char *fmt; + + // + // We first setup stuff before we start polling for stats + // + fd = open("/proc/net/dev", O_RDONLY); + if (fd < 0) + { + qWarning("Unable to open /proc/net/dev - no stats will be available"); + return; + } + + buf.fill('\0', 8192); + len = read(fd, (void*) buf.data(), buf.size()); + if (len < 0) + { + qWarning("initial buffer size is too small. no stats will be available"); + return; + } + + p = buf.data(); + end = p + len; + + // Select scanf format + if (strstr(buf, "compressed")) + fmt = fmtopt[0]; + else + fmt = fmtopt[1]; + + // Count number of lines - number of ports is 2 less than number of lines + count = 0; + while (p < end) + { + if (*p == '\n') + count++; + p++; + } + count -= 2; + + if (count <= 0) + { + qWarning("no ports in /proc/dev/net - no stats will be available"); + return; + } + + portStats = (PortStats**) calloc(count, sizeof(PortStats)); + Q_ASSERT(portStats != NULL); + + // + // Populate the port stats array + // + p = buf.data(); + + // Skip first two lines + while (*p != '\n') + p++; + p++; + while (*p != '\n') + p++; + p++; + + index = 0; + while (p < end) + { + char* q; + + // Skip whitespace + while ((p < end) && (*p == ' ')) + p++; + + q = p; + + // Get interface name + while ((q < end) && (*q != ':') && (*q != '\n')) + q++; + + if ((q < end) && (*q == ':')) + { + foreach(LinuxPort* port, allPorts_) + { + if (strncmp(port->name(), p, int(q-p)) == 0) + { + portStats[index] = &(port->stats_); + + if (setPromisc(port->name())) + port->clearPromisc_ = true; + else + port->isPromisc_ = false; + + break; + } + } + } + index++; + + // Skip till newline + p = q; + while (*p != '\n') + p++; + p++; + } + Q_ASSERT(index == count); + + qDebug("stats for %d ports setup", count); + setupDone_ = true; + + // + // We are all set - Let's start polling for stats! + // + while (!stop_) + { + lseek(fd, 0, SEEK_SET); + len = read(fd, (void*) buf.data(), buf.size()); + if (len < 0) + { + if (buf.size() > 1*1024*1024) + { + qWarning("buffer size hit limit. no more stats"); + return; + } + qDebug("doubling buffer size. curr = %d", buf.size()); + buf.resize(buf.size() * 2); + continue; + } + + p = buf.data(); + end = p + len; + + // Skip first two lines + while (*p != '\n') + p++; + p++; + while (*p != '\n') + p++; + p++; + + index = 0; + while (p < end) + { + uint dummy; + quint64 rxBytes, rxPkts; + quint64 rxErrors, rxDrops, rxFifo, rxFrame; + quint64 txBytes, txPkts; + + // Skip interface name - we assume the number and order of ports + // won't change since we parsed the output before we started polling + while ((p < end) && (*p != ':') && (*p != '\n')) + p++; + if (p >= end) + break; + if (*p == '\n') + { + index++; + continue; + } + p++; + + sscanf(p, fmt, + &rxBytes, &rxPkts, &rxErrors, &rxDrops, &rxFifo, &rxFrame, + &dummy, &dummy, + &txBytes, &txPkts, &dummy, &dummy, &dummy, &dummy, &dummy, + &dummy); + + if (index < count) + { + AbstractPort::PortStats *stats = portStats[index]; + if (stats) + { + // TODO: fix the pps/Bps calc similar to netlink stats + stats->rxPps = + ((rxPkts >= stats->rxPkts) ? + rxPkts - stats->rxPkts : + rxPkts + (kMaxValue32 - stats->rxPkts)) + / kRefreshFreq_; + stats->rxBps = + ((rxBytes >= stats->rxBytes) ? + rxBytes - stats->rxBytes : + rxBytes + (kMaxValue32 - stats->rxBytes)) + / kRefreshFreq_; + stats->rxPkts = rxPkts; + stats->rxBytes = rxBytes; + stats->txPps = + ((txPkts >= stats->txPkts) ? + txPkts - stats->txPkts : + txPkts + (kMaxValue32 - stats->txPkts)) + / kRefreshFreq_; + stats->txBps = + ((txBytes >= stats->txBytes) ? + txBytes - stats->txBytes : + txBytes + (kMaxValue32 - stats->txBytes)) + / kRefreshFreq_; + stats->txPkts = txPkts; + stats->txBytes = txBytes; + + stats->rxDrops = rxDrops; + stats->rxErrors = rxErrors; + stats->rxFifoErrors = rxFifo; + stats->rxFrameErrors = rxFrame; + } + } + + while (*p != '\n') + p++; + p++; + index++; + } + QThread::sleep(kRefreshFreq_); + } + + free(portStats); +} + +int LinuxPort::StatsMonitor::netlinkStats() +{ + QHash portStats; + QHash portMaxStatsValue; + QHash linkState; + int fd; + struct sockaddr_nl local; + struct sockaddr_nl kernel; + QByteArray buf; + int len, count; + struct { + struct nlmsghdr nlh; + struct rtgenmsg rtg; + } ifListReq; + struct iovec iov; + struct msghdr msg; + struct nlmsghdr *nlm; + bool done = false; + + // + // We first setup stuff before we start polling for stats + // + fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (fd < 0) + { + qWarning("Unable to open netlink socket (errno %d)", errno); + return -1; + } + + memset(&local, 0, sizeof(local)); + local.nl_family = AF_NETLINK; + + if (bind(fd, (struct sockaddr*) &local, sizeof(local)) < 0) + { + qWarning("Unable to bind netlink socket (errno %d)", errno); + return -1; + } + + memset(&ifListReq, 0, sizeof(ifListReq)); + ifListReq.nlh.nlmsg_len = sizeof(ifListReq); + ifListReq.nlh.nlmsg_type = RTM_GETLINK; + ifListReq.nlh.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + ifListReq.nlh.nlmsg_pid = 0; + ifListReq.rtg.rtgen_family = AF_PACKET; + + buf.fill('\0', 1024); + + msg.msg_name = &kernel; + msg.msg_namelen = sizeof(kernel); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + + qDebug("nlmsg_flags = %x", ifListReq.nlh.nlmsg_flags); + + if (send(fd, (void*)&ifListReq, sizeof(ifListReq), 0) < 0) + { + qWarning("Unable to send GETLINK request (errno %d)", errno); + return -1; + } + + count = 0; + +_retry: + + // Find required size of buffer and resize accordingly + while (1) + { + iov.iov_base = buf.data(); + iov.iov_len = buf.size(); + msg.msg_flags = 0; + + // Peek at reply to check buffer size required + len = recvmsg(fd, &msg, MSG_PEEK|MSG_TRUNC); + + if (len < 0) + { + if (errno == EINTR || errno == EAGAIN) + continue; + + qWarning("netlink recv error %d", errno); + return -1; + } + else if (len == 0) + { + qWarning("netlink closed the socket on my face!"); + return -1; + } + else + { + if (msg.msg_flags & MSG_TRUNC) + { + if (len == buf.size()) // Older Kernel returns truncated size + { + qDebug("netlink buffer size %d not enough", buf.size()); + qDebug("retrying with double the size"); + // Double the size and retry + buf.resize(buf.size()*2); + continue; + } + else // Newer Kernel returns actual size required + { + qDebug("netlink required buffer size = %d", len); + buf.resize(len); + continue; + } + } + else + qDebug("buffer size %d enough for netlink", buf.size()); + + break; + } + } + + msg.msg_flags = 0; + + // Actually receive the reply now + len = recvmsg(fd, &msg, 0); + + if (len < 0) + { + if (errno == EINTR || errno == EAGAIN) + goto _retry; + qWarning("netlink recv error %d", errno); + return -1; + } + else if (len == 0) + { + qWarning("netlink socket closed unexpectedly"); + return -1; + } + + // + // Populate the port stats hash table + // + nlm = (struct nlmsghdr*) buf.data(); + while (NLMSG_OK(nlm, (uint)len)) + { + struct ifinfomsg *ifi; + struct rtattr *rta; + int rtaLen; + char ifname[64] = ""; + + if (nlm->nlmsg_type == NLMSG_DONE) + { + done = true; + break; + } + + if (nlm->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err = (struct nlmsgerr*) NLMSG_DATA(nlm); + qDebug("RTNETLINK error %d", err->error); + done = true; + break; + } + + Q_ASSERT(nlm->nlmsg_type == RTM_NEWLINK); + + ifi = (struct ifinfomsg*) NLMSG_DATA(nlm); + rta = IFLA_RTA(ifi); + rtaLen = len - NLMSG_LENGTH(sizeof(*ifi)); + while (RTA_OK(rta, rtaLen)) + { + if (rta->rta_type == IFLA_IFNAME) + { + strncpy(ifname, (char*)RTA_DATA(rta), RTA_PAYLOAD(rta)); + ifname[RTA_PAYLOAD(rta)] = 0; + break; + } + rta = RTA_NEXT(rta, rtaLen); + } + + qDebug("if: %s(%d)", ifname, ifi->ifi_index); + foreach(LinuxPort* port, allPorts_) + { + if (strcmp(port->name(), ifname) == 0) + { + portStats[uint(ifi->ifi_index)] = &(port->stats_); + portMaxStatsValue[uint(ifi->ifi_index)] = + &(port->maxStatsValue_); + linkState[uint(ifi->ifi_index)] = &(port->linkState_); + + if (setPromisc(port->name())) + port->clearPromisc_ = true; + else + port->isPromisc_ = false; + + count++; + break; + } + } + nlm = NLMSG_NEXT(nlm, len); + } + + if (!done) + goto _retry; + + qDebug("port count = %d\n", count); + if (count <= 0) + { + qWarning("no ports in RTNETLINK GET_LINK - no stats will be available"); + return - 1; + } + + qDebug("stats for %d ports setup", count); + setupDone_ = true; + + // + // We are all set - Let's start polling for stats! + // + while (!stop_) + { + if (send(fd, (void*)&ifListReq, sizeof(ifListReq), 0) < 0) + { + qWarning("Unable to send GETLINK request (errno %d)", errno); + goto _try_later; + } + + done = false; + +_retry_recv: + msg.msg_flags = 0; + len = recvmsg(fd, &msg, 0); + + if (len < 0) + { + if (errno == EINTR || errno == EAGAIN) + goto _retry_recv; + qWarning("netlink recv error %d", errno); + break; + } + else if (len == 0) + { + qWarning("netlink socket closed unexpectedly"); + break; + } + + nlm = (struct nlmsghdr*) buf.data(); + while (NLMSG_OK(nlm, (uint)len)) + { + struct ifinfomsg *ifi; + struct rtattr *rta; + int rtaLen; + + if (nlm->nlmsg_type == NLMSG_DONE) + { + done = true; + break; + } + + if (nlm->nlmsg_type == NLMSG_ERROR) + { + struct nlmsgerr *err = (struct nlmsgerr*) NLMSG_DATA(nlm); + qDebug("RTNETLINK error: %s", strerror(-err->error)); + done = true; + break; + } + + Q_ASSERT(nlm->nlmsg_type == RTM_NEWLINK); + + ifi = (struct ifinfomsg*) NLMSG_DATA(nlm); + rta = IFLA_RTA(ifi); + rtaLen = len - NLMSG_LENGTH(sizeof(*ifi)); + while (RTA_OK(rta, rtaLen)) + { + if (rta->rta_type == X_IFLA_STATS) + { + x_rtnl_link_stats *rtnlStats = + (x_rtnl_link_stats*) RTA_DATA(rta); + AbstractPort::PortStats *stats = portStats[ifi->ifi_index]; + quint64 *maxStatsValue = portMaxStatsValue[ifi->ifi_index]; + OstProto::LinkState *state = linkState[ifi->ifi_index]; + + if (!stats) + break; + + if (rtnlStats->rx_packets >= stats->rxPkts) { + stats->rxPps = (rtnlStats->rx_packets - stats->rxPkts) + / kRefreshFreq_; + } + else { + if (*maxStatsValue == 0) { + *maxStatsValue = stats->rxPkts > kMaxValue32 ? + kMaxValue64 : kMaxValue32; + } + stats->rxPps = ((*maxStatsValue - stats->rxPkts) + + rtnlStats->rx_packets) + / kRefreshFreq_; + } + + if (rtnlStats->rx_bytes >= stats->rxBytes) { + stats->rxBps = (rtnlStats->rx_bytes - stats->rxBytes) + / kRefreshFreq_; + } + else { + if (*maxStatsValue == 0) { + *maxStatsValue = stats->rxBytes > kMaxValue32 ? + kMaxValue64 : kMaxValue32; + } + stats->rxBps = ((*maxStatsValue - stats->rxBytes) + + rtnlStats->rx_bytes) + / kRefreshFreq_; + } + + stats->rxPkts = rtnlStats->rx_packets; + stats->rxBytes = rtnlStats->rx_bytes; + + if (rtnlStats->tx_packets >= stats->txPkts) { + stats->txPps = (rtnlStats->tx_packets - stats->txPkts) + / kRefreshFreq_; + } + else { + if (*maxStatsValue == 0) { + *maxStatsValue = stats->txPkts > kMaxValue32 ? + kMaxValue64 : kMaxValue32; + } + stats->txPps = ((*maxStatsValue - stats->txPkts) + + rtnlStats->tx_packets) + / kRefreshFreq_; + } + + if (rtnlStats->tx_bytes >= stats->txBytes) { + stats->txBps = (rtnlStats->tx_bytes - stats->txBytes) + / kRefreshFreq_; + } + else { + if (*maxStatsValue == 0) { + *maxStatsValue = stats->txBytes > kMaxValue32 ? + kMaxValue64 : kMaxValue32; + } + stats->txBps = ((*maxStatsValue - stats->txBytes) + + rtnlStats->tx_bytes) + / kRefreshFreq_; + } + + stats->txPkts = rtnlStats->tx_packets; + stats->txBytes = rtnlStats->tx_bytes; + + // TODO: export detailed error stats + stats->rxDrops = rtnlStats->rx_dropped + + rtnlStats->rx_missed_errors; + stats->rxErrors = rtnlStats->rx_errors; + stats->rxFifoErrors = rtnlStats->rx_fifo_errors; + stats->rxFrameErrors = rtnlStats->rx_crc_errors + + rtnlStats->rx_length_errors + + rtnlStats->rx_over_errors + + rtnlStats->rx_frame_errors; + + Q_ASSERT(state); + *state = ifi->ifi_flags & IFF_RUNNING ? + OstProto::LinkStateUp : OstProto::LinkStateDown; + + break; + } + rta = RTA_NEXT(rta, rtaLen); + } + nlm = NLMSG_NEXT(nlm, len); + } + + if (!done) + goto _retry_recv; + +_try_later: + QThread::sleep(kRefreshFreq_); + } + + portStats.clear(); + linkState.clear(); + + return 0; +} + +int LinuxPort::StatsMonitor::setPromisc(const char * portName) +{ + struct ifreq ifr; + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, portName, sizeof(ifr.ifr_name)); + + if (ioctl(ioctlSocket_, SIOCGIFFLAGS, &ifr) != -1) + { + if ((ifr.ifr_flags & IFF_PROMISC) == 0) + { + ifr.ifr_flags |= IFF_PROMISC; + if (ioctl(ioctlSocket_, SIOCSIFFLAGS, &ifr) != -1) + { + return 1; + } + else + { + qDebug("%s: failed to set promisc; " + "SIOCSIFFLAGS failed (%s)", + portName, strerror(errno)); + } + } + } + else + { + qDebug("%s: failed to set promisc; SIOCGIFFLAGS failed (%s)", + portName, strerror(errno)); + } + + return 0; +} + +void LinuxPort::StatsMonitor::stop() +{ + stop_ = true; +} + +bool LinuxPort::StatsMonitor::waitForSetupFinished(int msecs) +{ + QTime t; + + t.start(); + while (!setupDone_) + { + if (t.elapsed() > msecs) + return false; + + QThread::msleep(10); + } + + return true; +} +#endif diff --git a/server/linuxport.h b/server/linuxport.h new file mode 100644 index 0000000..2658560 --- /dev/null +++ b/server/linuxport.h @@ -0,0 +1,68 @@ +/* +Copyright (C) 2011 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SERVER_LINUX_PORT_H +#define _SERVER_LINUX_PORT_H + +#include + +#ifdef Q_OS_LINUX + +#include "pcapport.h" + +class LinuxPort : public PcapPort +{ +public: + LinuxPort(int id, const char *device); + ~LinuxPort(); + + void init(); + + virtual OstProto::LinkState linkState(); + virtual bool hasExclusiveControl(); + virtual bool setExclusiveControl(bool exclusive); + +protected: + class StatsMonitor: public QThread + { + public: + StatsMonitor(); + ~StatsMonitor(); + void run(); + void stop(); + bool waitForSetupFinished(int msecs = 10000); + private: + int netlinkStats(); + void procStats(); + int setPromisc(const char* portName); + + static const int kRefreshFreq_ = 1; // in seconds + bool stop_; + bool setupDone_; + int ioctlSocket_; + }; + + bool isPromisc_; + bool clearPromisc_; + static QList allPorts_; + static StatsMonitor *monitor_; // rx/tx stats for ALL ports +}; +#endif + +#endif diff --git a/server/myservice.cpp b/server/myservice.cpp new file mode 100644 index 0000000..55f3bf3 --- /dev/null +++ b/server/myservice.cpp @@ -0,0 +1,581 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + + +#include "myservice.h" + +#if 0 +#include +#include +#include "qdebug.h" + +#include "../common/protocollistiterator.h" +#include "../common/abstractprotocol.h" +#endif + +#include "../common/streambase.h" +#include "../rpc/pbrpccontroller.h" +#include "portmanager.h" + +#include + + +extern char *version; + +MyService::MyService() +{ + PortManager *portManager = PortManager::instance(); + int n = portManager->portCount(); + + for (int i = 0; i < n; i++) { + portInfo.append(portManager->port(i)); + portLock.append(new QReadWriteLock()); + } +} + +MyService::~MyService() +{ + while (!portLock.isEmpty()) + delete portLock.takeFirst(); + //! \todo Use a singleton destroyer instead + // http://www.research.ibm.com/designpatterns/pubs/ph-jun96.txt + delete PortManager::instance(); +} + +void MyService::getPortIdList(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::Void* /*request*/, + ::OstProto::PortIdList* response, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + // No locks are needed here because the list does not change + // and neither does the port_id + + for (int i = 0; i < portInfo.size(); i++) + { + ::OstProto::PortId *p; + + p = response->add_port_id(); + p->set_id(portInfo[i]->id()); + } + + done->Run(); +} + +void MyService::getPortConfig(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortIdList* request, + ::OstProto::PortConfigList* response, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_id_size(); i++) + { + int id; + + id = request->port_id(i).id(); + if (id < portInfo.size()) + { + OstProto::Port *p; + + p = response->add_port(); + portLock[id]->lockForRead(); + portInfo[id]->protoDataCopyInto(p); + portLock[id]->unlock(); + } + } + + done->Run(); +} + +void MyService::modifyPort(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortConfigList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_size(); i++) + { + OstProto::Port port; + int id; + + port = request->port(i); + id = port.port_id().id(); + if (id < portInfo.size()) + { + portLock[id]->lockForWrite(); + portInfo[id]->modify(port); + portLock[id]->unlock(); + } + } + + //! \todo (LOW): fill-in response "Ack"???? + done->Run(); +} + +void MyService::getStreamIdList(::google::protobuf::RpcController* controller, + const ::OstProto::PortId* request, + ::OstProto::StreamIdList* response, + ::google::protobuf::Closure* done) +{ + int portId; + + qDebug("In %s", __PRETTY_FUNCTION__); + + portId = request->id(); + if ((portId < 0) || (portId >= portInfo.size())) + goto _invalid_port; + + response->mutable_port_id()->set_id(portId); + portLock[portId]->lockForRead(); + for (int i = 0; i < portInfo[portId]->streamCount(); i++) + { + OstProto::StreamId *s; + + s = response->add_stream_id(); + s->set_id(portInfo[portId]->streamAtIndex(i)->id()); + } + portLock[portId]->unlock(); + + done->Run(); + return; + +_invalid_port: + controller->SetFailed("Invalid Port Id"); + done->Run(); +} + +void MyService::getStreamConfig(::google::protobuf::RpcController* controller, + const ::OstProto::StreamIdList* request, + ::OstProto::StreamConfigList* response, + ::google::protobuf::Closure* done) +{ + int portId; + + qDebug("In %s", __PRETTY_FUNCTION__); + + portId = request->port_id().id(); + if ((portId < 0) || (portId >= portInfo.size())) + goto _invalid_port; + + response->mutable_port_id()->set_id(portId); + portLock[portId]->lockForRead(); + for (int i = 0; i < request->stream_id_size(); i++) + { + StreamBase *stream; + OstProto::Stream *s; + + stream = portInfo[portId]->stream(request->stream_id(i).id()); + if (!stream) + continue; //! \todo(LOW): Partial status of RPC + + s = response->add_stream(); + stream->protoDataCopyInto(*s); + } + portLock[portId]->unlock(); + + done->Run(); + return; + +_invalid_port: + controller->SetFailed("invalid portid"); + done->Run(); +} + +void MyService::addStream(::google::protobuf::RpcController* controller, + const ::OstProto::StreamIdList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + int portId; + + qDebug("In %s", __PRETTY_FUNCTION__); + + portId = request->port_id().id(); + if ((portId < 0) || (portId >= portInfo.size())) + goto _invalid_port; + + if (portInfo[portId]->isTransmitOn()) + goto _port_busy; + + portLock[portId]->lockForWrite(); + for (int i = 0; i < request->stream_id_size(); i++) + { + StreamBase *stream; + + // If stream with same id as in request exists already ==> error!! + stream = portInfo[portId]->stream(request->stream_id(i).id()); + if (stream) + continue; //! \todo (LOW): Partial status of RPC + + // Append a new "default" stream - actual contents of the new stream is + // expected in a subsequent "modifyStream" request - set the stream id + // now itself however!!! + stream = new StreamBase; + stream->setId(request->stream_id(i).id()); + portInfo[portId]->addStream(stream); + } + portLock[portId]->unlock(); + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); + return; + +_port_busy: + controller->SetFailed("Port Busy"); + goto _exit; + +_invalid_port: + controller->SetFailed("invalid portid"); +_exit: + done->Run(); +} + +void MyService::deleteStream(::google::protobuf::RpcController* controller, + const ::OstProto::StreamIdList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + int portId; + + qDebug("In %s", __PRETTY_FUNCTION__); + + portId = request->port_id().id(); + if ((portId < 0) || (portId >= portInfo.size())) + goto _invalid_port; + + if (portInfo[portId]->isTransmitOn()) + goto _port_busy; + + portLock[portId]->lockForWrite(); + for (int i = 0; i < request->stream_id_size(); i++) + portInfo[portId]->deleteStream(request->stream_id(i).id()); + portLock[portId]->unlock(); + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); + return; + +_port_busy: + controller->SetFailed("Port Busy"); + goto _exit; +_invalid_port: + controller->SetFailed("invalid portid"); +_exit: + done->Run(); +} + +void MyService::modifyStream(::google::protobuf::RpcController* controller, + const ::OstProto::StreamConfigList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + int portId; + + qDebug("In %s", __PRETTY_FUNCTION__); + + portId = request->port_id().id(); + if ((portId < 0) || (portId >= portInfo.size())) + goto _invalid_port; + + if (portInfo[portId]->isTransmitOn()) + goto _port_busy; + + portLock[portId]->lockForWrite(); + for (int i = 0; i < request->stream_size(); i++) + { + StreamBase *stream; + + stream = portInfo[portId]->stream(request->stream(i).stream_id().id()); + if (stream) + { + stream->protoDataCopyFrom(request->stream(i)); + portInfo[portId]->setDirty(); + } + } + + if (portInfo[portId]->isDirty()) + portInfo[portId]->updatePacketList(); + portLock[portId]->unlock(); + + //! \todo(LOW): fill-in response "Ack"???? + + done->Run(); + return; + +_port_busy: + controller->SetFailed("Port Busy"); + goto _exit; +_invalid_port: + controller->SetFailed("invalid portid"); +_exit: + done->Run(); +} + +void MyService::startTransmit(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_id_size(); i++) + { + int portId; + + portId = request->port_id(i).id(); + if ((portId < 0) || (portId >= portInfo.size())) + continue; //! \todo (LOW): partial RPC? + + portLock[portId]->lockForWrite(); + portInfo[portId]->startTransmit(); + portLock[portId]->unlock(); + } + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); +} + +void MyService::stopTransmit(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_id_size(); i++) + { + int portId; + + portId = request->port_id(i).id(); + if ((portId < 0) || (portId >= portInfo.size())) + continue; //! \todo (LOW): partial RPC? + + portLock[portId]->lockForWrite(); + portInfo[portId]->stopTransmit(); + portLock[portId]->unlock(); + } + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); +} + +void MyService::startCapture(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_id_size(); i++) + { + int portId; + + portId = request->port_id(i).id(); + if ((portId < 0) || (portId >= portInfo.size())) + continue; //! \todo (LOW): partial RPC? + + portLock[portId]->lockForWrite(); + portInfo[portId]->startCapture(); + portLock[portId]->unlock(); + } + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); +} + +void MyService::stopCapture(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + for (int i=0; i < request->port_id_size(); i++) + { + int portId; + + portId = request->port_id(i).id(); + if ((portId < 0) || (portId >= portInfo.size())) + continue; //! \todo (LOW): partial RPC? + + portLock[portId]->lockForWrite(); + portInfo[portId]->stopCapture(); + portLock[portId]->unlock(); + } + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); +} + +void MyService::getCaptureBuffer(::google::protobuf::RpcController* controller, + const ::OstProto::PortId* request, + ::OstProto::CaptureBuffer* /*response*/, + ::google::protobuf::Closure* done) +{ + int portId; + + qDebug("In %s", __PRETTY_FUNCTION__); + + portId = request->id(); + if ((portId < 0) || (portId >= portInfo.size())) + goto _invalid_port; + + portLock[portId]->lockForWrite(); + portInfo[portId]->stopCapture(); + static_cast(controller)->setBinaryBlob( + portInfo[portId]->captureData()); + portLock[portId]->unlock(); + + done->Run(); + return; + +_invalid_port: + controller->SetFailed("invalid portid"); + done->Run(); +} + +void MyService::getStats(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortIdList* request, + ::OstProto::PortStatsList* response, + ::google::protobuf::Closure* done) +{ + //qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_id_size(); i++) + { + int portId; + AbstractPort::PortStats stats; + OstProto::PortStats *s; + OstProto::PortState *st; + + portId = request->port_id(i).id(); + if ((portId < 0) || (portId >= portInfo.size())) + continue; //! \todo(LOW): partial rpc? + + s = response->add_port_stats(); + s->mutable_port_id()->set_id(request->port_id(i).id()); + + st = s->mutable_state(); + portLock[portId]->lockForRead(); + st->set_link_state(portInfo[portId]->linkState()); + st->set_is_transmit_on(portInfo[portId]->isTransmitOn()); + st->set_is_capture_on(portInfo[portId]->isCaptureOn()); + + portInfo[portId]->stats(&stats); + portLock[portId]->unlock(); + +#if 0 + if (portId == 2) + qDebug(">%llu", stats.rxPkts); +#endif + + s->set_rx_pkts(stats.rxPkts); + s->set_rx_bytes(stats.rxBytes); + s->set_rx_pps(stats.rxPps); + s->set_rx_bps(stats.rxBps); + + s->set_tx_pkts(stats.txPkts); + s->set_tx_bytes(stats.txBytes); + s->set_tx_pps(stats.txPps); + s->set_tx_bps(stats.txBps); + + s->set_rx_drops(stats.rxDrops); + s->set_rx_errors(stats.rxErrors); + s->set_rx_fifo_errors(stats.rxFifoErrors); + s->set_rx_frame_errors(stats.rxFrameErrors); + } + + done->Run(); +} + +void MyService::clearStats(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* /*response*/, + ::google::protobuf::Closure* done) +{ + qDebug("In %s", __PRETTY_FUNCTION__); + + for (int i = 0; i < request->port_id_size(); i++) + { + int portId; + + portId = request->port_id(i).id(); + if ((portId < 0) || (portId >= portInfo.size())) + continue; //! \todo (LOW): partial RPC? + + portLock[portId]->lockForWrite(); + portInfo[portId]->resetStats(); + portLock[portId]->unlock(); + } + + //! \todo (LOW): fill-in response "Ack"???? + + done->Run(); +} + +void MyService::checkVersion(::google::protobuf::RpcController* controller, + const ::OstProto::VersionInfo* request, + ::OstProto::VersionCompatibility* response, + ::google::protobuf::Closure* done) +{ + QString myVersion(version); + QString clientVersion; + QStringList my, client; + + qDebug("In %s", __PRETTY_FUNCTION__); + + my = myVersion.split('.'); + + Q_ASSERT(my.size() >= 2); + + clientVersion = QString::fromStdString(request->version()); + client = clientVersion.split('.'); + + qDebug("client = %s, my = %s", + qPrintable(clientVersion), qPrintable(myVersion)); + + if (client.size() < 2) + goto _invalid_version; + + // Compare only major and minor numbers + if (client[0] == my[0] && client[1] == my[1]) { + response->set_result(OstProto::VersionCompatibility::kCompatible); + } + else { + response->set_result(OstProto::VersionCompatibility::kIncompatible); + response->set_notes(QString("Drone needs client version %1.%2.x") + .arg(my[0], my[1]).toStdString()); + static_cast(controller)->TriggerDisconnect(); + } + + done->Run(); + return; + +_invalid_version: + controller->SetFailed("invalid version information"); + done->Run(); +} diff --git a/server/myservice.h b/server/myservice.h new file mode 100644 index 0000000..15c2f5f --- /dev/null +++ b/server/myservice.h @@ -0,0 +1,121 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _MY_SERVICE_H +#define _MY_SERVICE_H + +#include "../common/protocol.pb.h" + +#include +#include + +#define MAX_PKT_HDR_SIZE 1536 +#define MAX_STREAM_NAME_SIZE 64 + +class AbstractPort; + +class MyService: public OstProto::OstService +{ +public: + MyService(); + virtual ~MyService(); + + /* Methods provided by the service */ + virtual void getPortIdList(::google::protobuf::RpcController* controller, + const ::OstProto::Void* request, + ::OstProto::PortIdList* response, + ::google::protobuf::Closure* done); + virtual void getPortConfig(::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::PortConfigList* response, + ::google::protobuf::Closure* done); + virtual void modifyPort(::google::protobuf::RpcController* /*controller*/, + const ::OstProto::PortConfigList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void getStreamIdList(::google::protobuf::RpcController* controller, + const ::OstProto::PortId* request, + ::OstProto::StreamIdList* response, + ::google::protobuf::Closure* done); + virtual void getStreamConfig(::google::protobuf::RpcController* controller, + const ::OstProto::StreamIdList* request, + ::OstProto::StreamConfigList* response, + ::google::protobuf::Closure* done); + virtual void addStream(::google::protobuf::RpcController* controller, + const ::OstProto::StreamIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void deleteStream(::google::protobuf::RpcController* controller, + const ::OstProto::StreamIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void modifyStream(::google::protobuf::RpcController* controller, + const ::OstProto::StreamConfigList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void startTransmit(::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void stopTransmit(::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void startCapture(::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void stopCapture(::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void getCaptureBuffer(::google::protobuf::RpcController* controller, + const ::OstProto::PortId* request, + ::OstProto::CaptureBuffer* response, + ::google::protobuf::Closure* done); + virtual void getStats(::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::PortStatsList* response, + ::google::protobuf::Closure* done); + virtual void clearStats(::google::protobuf::RpcController* controller, + const ::OstProto::PortIdList* request, + ::OstProto::Ack* response, + ::google::protobuf::Closure* done); + virtual void checkVersion(::google::protobuf::RpcController* controller, + const ::OstProto::VersionInfo* request, + ::OstProto::VersionCompatibility* response, + ::google::protobuf::Closure* done); + +private: + /* + * NOTES: + * - AbstractPort::id() and index into portInfo[] are same! + * - portLock[] size and order should be same as portInfo[] as the + * same index is used for both. + * - we assume that once populated by the constructor, the list(s) + * never change (objects in the list can change, but not the list itself) + * - locking is at port granularity, not at stream granularity - for now + * this seems sufficient. Revisit later, if required + */ + QList portInfo; + QList portLock; + +}; + +#endif diff --git a/server/pcapextra.cpp b/server/pcapextra.cpp new file mode 100644 index 0000000..4acbda9 --- /dev/null +++ b/server/pcapextra.cpp @@ -0,0 +1,78 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pcapextra.h" + +#include // memcpy() +#include // malloc(), free() + +/* NOTE: All code borrowed from WinPcap */ + +#ifndef Q_OS_WIN32 +pcap_send_queue* pcap_sendqueue_alloc (u_int memsize) +{ + pcap_send_queue *tqueue; + + /* Allocate the queue */ + tqueue = (pcap_send_queue*)malloc(sizeof(pcap_send_queue)); + if(tqueue == NULL){ + return NULL; + } + + /* Allocate the buffer */ + tqueue->buffer = (char*)malloc(memsize); + if(tqueue->buffer == NULL){ + free(tqueue); + return NULL; + } + + tqueue->maxlen = memsize; + tqueue->len = 0; + + return tqueue; +} + +void pcap_sendqueue_destroy (pcap_send_queue *queue) +{ + free(queue->buffer); + free(queue); +} + +int pcap_sendqueue_queue (pcap_send_queue *queue, + const struct pcap_pkthdr *pkt_header, const u_char *pkt_data) +{ + if(queue->len + sizeof(struct pcap_pkthdr) + pkt_header->caplen > + queue->maxlen) + { + return -1; + } + + /* Copy the pcap_pkthdr header*/ + memcpy(queue->buffer + queue->len, pkt_header, sizeof(struct pcap_pkthdr)); + queue->len += sizeof(struct pcap_pkthdr); + + /* copy the packet */ + memcpy(queue->buffer + queue->len, pkt_data, pkt_header->caplen); + queue->len += pkt_header->caplen; + + return 0; +} +#endif + + diff --git a/server/pcapextra.h b/server/pcapextra.h new file mode 100644 index 0000000..415fe3e --- /dev/null +++ b/server/pcapextra.h @@ -0,0 +1,45 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _PCAP_EXTRA_H +#define _PCAP_EXTRA_H + +#include +#include + +#ifndef Q_OS_WIN32 + +#define PCAP_OPENFLAG_PROMISCUOUS 1 + +struct pcap_send_queue +{ + u_int maxlen; + u_int len; + char *buffer; +}; + +pcap_send_queue* pcap_sendqueue_alloc (u_int memsize); +void pcap_sendqueue_destroy (pcap_send_queue *queue); +int pcap_sendqueue_queue (pcap_send_queue *queue, + const struct pcap_pkthdr *pkt_header, const u_char *pkt_data); + +#endif + +#endif + diff --git a/server/pcapport.cpp b/server/pcapport.cpp new file mode 100644 index 0000000..c46d5ad --- /dev/null +++ b/server/pcapport.cpp @@ -0,0 +1,857 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "pcapport.h" + +#include + +#ifdef Q_OS_WIN32 +#include +#endif + +pcap_if_t *PcapPort::deviceList_ = NULL; + + +#if defined(Q_OS_LINUX) +typedef struct timeval TimeStamp; +static void inline getTimeStamp(TimeStamp *stamp) +{ + gettimeofday(stamp, NULL); +} + +// Returns time diff in usecs between end and start +static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end) +{ + struct timeval diff; + long usecs; + + timersub(end, start, &diff); + + usecs = diff.tv_usec; + if (diff.tv_sec) + usecs += diff.tv_sec*1e6; + + return usecs; +} +#elif defined(Q_OS_WIN32) +static quint64 gTicksFreq; +typedef LARGE_INTEGER TimeStamp; +static void inline getTimeStamp(TimeStamp* stamp) +{ + QueryPerformanceCounter(stamp); +} + +static long inline udiffTimeStamp(const TimeStamp *start, const TimeStamp *end) +{ + if (end->QuadPart >= start->QuadPart) + return (end->QuadPart - start->QuadPart)*long(1e6)/gTicksFreq; + else + { + // FIXME: incorrect! what's the max value for this counter before + // it rolls over? + return (start->QuadPart)*long(1e6)/gTicksFreq; + } +} +#else +typedef int TimeStamp; +static void inline getTimeStamp(TimeStamp*) {} +static long inline udiffTimeStamp(const TimeStamp*, const TimeStamp*) { return 0; } +#endif + +PcapPort::PcapPort(int id, const char *device) + : AbstractPort(id, device) +{ + monitorRx_ = new PortMonitor(device, kDirectionRx, &stats_); + monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_); + transmitter_ = new PortTransmitter(device); + capturer_ = new PortCapturer(device); + + if (!monitorRx_->handle() || !monitorTx_->handle()) + isUsable_ = false; + + if (!deviceList_) + { + char errbuf[PCAP_ERRBUF_SIZE]; + + if (pcap_findalldevs(&deviceList_, errbuf) == -1) + qDebug("Error in pcap_findalldevs_ex: %s\n", errbuf); + } + + for (pcap_if_t *dev = deviceList_; dev != NULL; dev = dev->next) + { + if (strcmp(device, dev->name) == 0) + { +#ifdef Q_OS_WIN32 + data_.set_name(QString("if%1").arg(id).toStdString()); +#else + if (dev->name) + data_.set_name(dev->name); +#endif + if (dev->description) + data_.set_description(dev->description); + + //! \todo set port IP addr also + } + } +} + +void PcapPort::init() +{ + if (!monitorTx_->isDirectional()) + transmitter_->useExternalStats(&stats_); + + transmitter_->setHandle(monitorRx_->handle()); + + updateNotes(); + + monitorRx_->start(); + monitorTx_->start(); +} + +PcapPort::~PcapPort() +{ + qDebug("In %s", __FUNCTION__); + + if (monitorRx_) + monitorRx_->stop(); + if (monitorTx_) + monitorTx_->stop(); + + delete capturer_; + delete transmitter_; + + if (monitorRx_) + monitorRx_->wait(); + delete monitorRx_; + + if (monitorTx_) + monitorTx_->wait(); + delete monitorTx_; +} + +void PcapPort::updateNotes() +{ + QString notes; + + if ((!monitorRx_->isPromiscuous()) || (!monitorTx_->isPromiscuous())) + notes.append("
  • Non Promiscuous Mode
  • "); + + if (!monitorRx_->isDirectional() && !hasExclusiveControl()) + notes.append("
  • Rx Frames/Bytes: Includes non Ostinato Tx pkts also (Tx by Ostinato are not included)
  • "); + + if (!monitorTx_->isDirectional() && !hasExclusiveControl()) + notes.append("
  • Tx Frames/Bytes: Only Ostinato Tx pkts (Tx by others NOT included)
  • "); + + if (notes.isEmpty()) + data_.set_notes(""); + else + data_.set_notes(QString("Limitation(s)" + "
      %1
    " + "Rx/Tx Rates are also subject to above limitation(s)"). + arg(notes).toStdString()); +} + +PcapPort::PortMonitor::PortMonitor(const char *device, Direction direction, + AbstractPort::PortStats *stats) +{ + int ret; + char errbuf[PCAP_ERRBUF_SIZE] = ""; + bool noLocalCapture; + + direction_ = direction; + isDirectional_ = true; + isPromisc_ = true; + noLocalCapture = true; + stats_ = stats; + stop_ = false; + +_retry: +#ifdef Q_OS_WIN32 + int flags = 0; + + if (isPromisc_) + flags |= PCAP_OPENFLAG_PROMISCUOUS; + if (noLocalCapture) + flags |= PCAP_OPENFLAG_NOCAPTURE_LOCAL; + + handle_ = pcap_open(device, 64 /* FIXME */, flags, + 1000 /* ms */, NULL, errbuf); +#else + handle_ = pcap_open_live(device, 64 /* FIXME */, int(isPromisc_), + 1000 /* ms */, errbuf); +#endif + + if (handle_ == NULL) + { + if (isPromisc_ && QString(errbuf).contains("promiscuous")) + { + qDebug("Can't set promiscuous mode, trying non-promisc %s", device); + isPromisc_ = false; + goto _retry; + } + else if (noLocalCapture && QString(errbuf).contains("loopback")) + { + qDebug("Can't set no local capture mode %s", device); + noLocalCapture = false; + goto _retry; + } + else + goto _open_error; + } +#ifdef Q_OS_WIN32 + // pcap_setdirection() API is not supported in Windows. + // NOTE: WinPcap 4.1.1 and above exports a dummy API that returns -1 + // but since we would like to work with previous versions of WinPcap + // also, we assume the API does not exist + ret = -1; +#else + switch (direction_) + { + case kDirectionRx: + ret = pcap_setdirection(handle_, PCAP_D_IN); + break; + case kDirectionTx: + ret = pcap_setdirection(handle_, PCAP_D_OUT); + break; + default: + ret = -1; // avoid 'may be used uninitialized' warning + Q_ASSERT(false); + } +#endif + + if (ret < 0) + goto _set_direction_error; + + return; + +_set_direction_error: + qDebug("Error setting direction(%d) %s: %s\n", direction, device, + pcap_geterr(handle_)); + isDirectional_ = false; + return; + +_open_error: + qDebug("%s: Error opening port %s: %s\n", __FUNCTION__, device, errbuf); +} + +PcapPort::PortMonitor::~PortMonitor() +{ + if (handle_) + pcap_close(handle_); +} + +void PcapPort::PortMonitor::run() +{ + while (!stop_) + { + int ret; + struct pcap_pkthdr *hdr; + const uchar *data; + + ret = pcap_next_ex(handle_, &hdr, &data); + switch (ret) + { + case 1: + switch (direction_) + { + case kDirectionRx: + stats_->rxPkts++; + stats_->rxBytes += hdr->len; + break; + + case kDirectionTx: + if (isDirectional_) + { + stats_->txPkts++; + stats_->txBytes += hdr->len; + } + break; + + default: + Q_ASSERT(false); + } + + //! \todo TODO pkt/bit rates + break; + case 0: + //qDebug("%s: timeout. continuing ...", __PRETTY_FUNCTION__); + continue; + case -1: + qWarning("%s: error reading packet (%d): %s", + __PRETTY_FUNCTION__, ret, pcap_geterr(handle_)); + break; + case -2: + qWarning("%s: error reading packet (%d): %s", + __PRETTY_FUNCTION__, ret, pcap_geterr(handle_)); + break; + default: + qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__, ret); + } + } +} + +void PcapPort::PortMonitor::stop() +{ + stop_ = true; + pcap_breakloop(handle()); +} + +PcapPort::PortTransmitter::PortTransmitter(const char *device) +{ + char errbuf[PCAP_ERRBUF_SIZE] = ""; + +#ifdef Q_OS_WIN32 + LARGE_INTEGER freq; + if (QueryPerformanceFrequency(&freq)) + gTicksFreq = ticksFreq_ = freq.QuadPart; + else + Q_ASSERT_X(false, "PortTransmitter::PortTransmitter", + "This Win32 platform does not support performance counter"); +#endif + state_ = kNotStarted; + returnToQIdx_ = -1; + loopDelay_ = 0; + stop_ = false; + stats_ = new AbstractPort::PortStats; + usingInternalStats_ = true; + handle_ = pcap_open_live(device, 64 /* FIXME */, 0, 1000 /* ms */, errbuf); + + if (handle_ == NULL) + goto _open_error; + + usingInternalHandle_ = true; + + return; + +_open_error: + qDebug("%s: Error opening port %s: %s\n", __FUNCTION__, device, errbuf); + usingInternalHandle_ = false; +} + +PcapPort::PortTransmitter::~PortTransmitter() +{ + if (usingInternalStats_) + delete stats_; + if (usingInternalHandle_) + pcap_close(handle_); +} + +void PcapPort::PortTransmitter::clearPacketList() +{ + Q_ASSERT(!isRunning()); + // \todo lock for packetSequenceList + while(packetSequenceList_.size()) + delete packetSequenceList_.takeFirst(); + + currentPacketSequence_ = NULL; + repeatSequenceStart_ = -1; + repeatSize_ = 0; + packetCount_ = 0; + + returnToQIdx_ = -1; + + setPacketListLoopMode(false, 0, 0); +} + +void PcapPort::PortTransmitter::loopNextPacketSet(qint64 size, qint64 repeats, + long repeatDelaySec, long repeatDelayNsec) +{ + currentPacketSequence_ = new PacketSequence; + currentPacketSequence_->repeatCount_ = repeats; + currentPacketSequence_->usecDelay_ = repeatDelaySec * long(1e6) + + repeatDelayNsec/1000; + + repeatSequenceStart_ = packetSequenceList_.size(); + repeatSize_ = size; + packetCount_ = 0; + + packetSequenceList_.append(currentPacketSequence_); +} + +bool PcapPort::PortTransmitter::appendToPacketList(long sec, long nsec, + const uchar *packet, int length) +{ + bool op = true; + pcap_pkthdr pktHdr; + + pktHdr.caplen = pktHdr.len = length; + pktHdr.ts.tv_sec = sec; + pktHdr.ts.tv_usec = nsec/1000; + + if (currentPacketSequence_ == NULL || + !currentPacketSequence_->hasFreeSpace(2*sizeof(pcap_pkthdr)+length)) + { + if (currentPacketSequence_ != NULL) + { + long usecs; + + usecs = (pktHdr.ts.tv_sec + - currentPacketSequence_->lastPacket_->ts.tv_sec) + * long(1e6); + usecs += (pktHdr.ts.tv_usec + - currentPacketSequence_->lastPacket_->ts.tv_usec); + currentPacketSequence_->usecDelay_ = usecs; + } + + //! \todo (LOW): calculate sendqueue size + currentPacketSequence_ = new PacketSequence; + + packetSequenceList_.append(currentPacketSequence_); + + // Validate that the pkt will fit inside the new currentSendQueue_ + Q_ASSERT(currentPacketSequence_->hasFreeSpace( + sizeof(pcap_pkthdr) + length)); + } + + if (currentPacketSequence_->appendPacket(&pktHdr, (u_char*) packet) < 0) + { + op = false; + } + + packetCount_++; + if (repeatSize_ > 0 && packetCount_ == repeatSize_) + { + qDebug("repeatSequenceStart_=%d, repeatSize_ = %llu", + repeatSequenceStart_, repeatSize_); + + // Set the packetSequence repeatSize + Q_ASSERT(repeatSequenceStart_ >= 0); + Q_ASSERT(repeatSequenceStart_ < packetSequenceList_.size()); + + if (currentPacketSequence_ != packetSequenceList_[repeatSequenceStart_]) + { + PacketSequence *start = packetSequenceList_[repeatSequenceStart_]; + + currentPacketSequence_->usecDelay_ = start->usecDelay_; + start->usecDelay_ = 0; + start->repeatSize_ = + packetSequenceList_.size() - repeatSequenceStart_; + } + + repeatSize_ = 0; + + // End current pktSeq and trigger a new pktSeq allocation for next pkt + currentPacketSequence_ = NULL; + } + + return op; +} + +void PcapPort::PortTransmitter::setHandle(pcap_t *handle) +{ + if (usingInternalHandle_) + pcap_close(handle_); + handle_ = handle; + usingInternalHandle_ = false; +} + +void PcapPort::PortTransmitter::useExternalStats(AbstractPort::PortStats *stats) +{ + if (usingInternalStats_) + delete stats_; + stats_ = stats; + usingInternalStats_ = false; +} + +void PcapPort::PortTransmitter::run() +{ + //! \todo (MED) Stream Mode - continuous: define before implement + + // NOTE1: We can't use pcap_sendqueue_transmit() directly even on Win32 + // 'coz of 2 reasons - there's no way of stopping it before all packets + // in the sendQueue are sent out and secondly, stats are available only + // when all packets have been sent - no periodic updates + // + // NOTE2: Transmit on the Rx Handle so that we can receive it back + // on the Tx Handle to do stats + // + // NOTE3: Update pcapExtra counters - port TxStats will be updated in the + // 'stats callback' function so that both Rx and Tx stats are updated + // together + + const int kSyncTransmit = 1; + int i; + long overHead = 0; // overHead should be negative or zero + + qDebug("packetSequenceList_.size = %d", packetSequenceList_.size()); + if (packetSequenceList_.size() <= 0) + goto _exit; + + for(i = 0; i < packetSequenceList_.size(); i++) { + qDebug("sendQ[%d]: rptCnt = %d, rptSz = %d, usecDelay = %ld", i, + packetSequenceList_.at(i)->repeatCount_, + packetSequenceList_.at(i)->repeatSize_, + packetSequenceList_.at(i)->usecDelay_); + qDebug("sendQ[%d]: pkts = %ld, usecDuration = %ld", i, + packetSequenceList_.at(i)->packets_, + packetSequenceList_.at(i)->usecDuration_); + } + + state_ = kRunning; + i = 0; + while (i < packetSequenceList_.size()) + { + +_restart: + int rptSz = packetSequenceList_.at(i)->repeatSize_; + int rptCnt = packetSequenceList_.at(i)->repeatCount_; + + for (int j = 0; j < rptCnt; j++) + { + for (int k = 0; k < rptSz; k++) + { + int ret; + PacketSequence *seq = packetSequenceList_.at(i+k); +#ifdef Q_OS_WIN32 + TimeStamp ovrStart, ovrEnd; + + if (seq->usecDuration_ <= long(1e6)) // 1s + { + getTimeStamp(&ovrStart); + ret = pcap_sendqueue_transmit(handle_, + seq->sendQueue_, kSyncTransmit); + if (ret >= 0) + { + stats_->txPkts += seq->packets_; + stats_->txBytes += seq->bytes_; + + getTimeStamp(&ovrEnd); + overHead += seq->usecDuration_ + - udiffTimeStamp(&ovrStart, &ovrEnd); + Q_ASSERT(overHead <= 0); + } + if (stop_) + ret = -2; + } + else + { + ret = sendQueueTransmit(handle_, seq->sendQueue_, + overHead, kSyncTransmit); + } +#else + ret = sendQueueTransmit(handle_, seq->sendQueue_, + overHead, kSyncTransmit); +#endif + + if (ret >= 0) + { + long usecs = seq->usecDelay_ + overHead; + if (usecs > 0) + { + udelay(usecs); + overHead = 0; + } + else + overHead = usecs; + } + else + { + qDebug("error %d in sendQueueTransmit()", ret); + qDebug("overHead = %ld", overHead); + stop_ = false; + goto _exit; + } + } + } + + // Move to the next Packet Set + i += rptSz; + } + + if (returnToQIdx_ >= 0) + { + long usecs = loopDelay_ + overHead; + + if (usecs > 0) + { + udelay(usecs); + overHead = 0; + } + else + overHead = usecs; + + i = returnToQIdx_; + goto _restart; + } + +_exit: + state_ = kFinished; +} + +void PcapPort::PortTransmitter::start() +{ + // FIXME: return error + if (state_ == kRunning) { + qWarning("Transmit start requested but is already running!"); + return; + } + + state_ = kNotStarted; + QThread::start(); + + while (state_ == kNotStarted) + QThread::msleep(10); +} + +void PcapPort::PortTransmitter::stop() +{ + if (state_ == kRunning) { + stop_ = true; + while (state_ == kRunning) + QThread::msleep(10); + } + else { + // FIXME: return error + qWarning("Transmit stop requested but is not running!"); + return; + } +} + +bool PcapPort::PortTransmitter::isRunning() +{ + return (state_ == kRunning); +} + +int PcapPort::PortTransmitter::sendQueueTransmit(pcap_t *p, + pcap_send_queue *queue, long &overHead, int sync) +{ + TimeStamp ovrStart, ovrEnd; + struct timeval ts; + struct pcap_pkthdr *hdr = (struct pcap_pkthdr*) queue->buffer; + char *end = queue->buffer + queue->len; + + ts = hdr->ts; + + getTimeStamp(&ovrStart); + while((char*) hdr < end) + { + uchar *pkt = (uchar*)hdr + sizeof(*hdr); + int pktLen = hdr->caplen; + + if (sync) + { + long usec = (hdr->ts.tv_sec - ts.tv_sec) * 1000000 + + (hdr->ts.tv_usec - ts.tv_usec); + + getTimeStamp(&ovrEnd); + + overHead -= udiffTimeStamp(&ovrStart, &ovrEnd); + Q_ASSERT(overHead <= 0); + usec += overHead; + if (usec > 0) + { + udelay(usec); + overHead = 0; + } + else + overHead = usec; + + ts = hdr->ts; + getTimeStamp(&ovrStart); + } + + Q_ASSERT(pktLen > 0); + + pcap_sendpacket(p, pkt, pktLen); + stats_->txPkts++; + stats_->txBytes += pktLen; + + // Step to the next packet in the buffer + hdr = (struct pcap_pkthdr*) (pkt + pktLen); + pkt = (uchar*) ((uchar*)hdr + sizeof(*hdr)); + + if (stop_) + { + return -2; + } + } + + return 0; +} + +void PcapPort::PortTransmitter::udelay(long usec) +{ +#if defined(Q_OS_WIN32) + LARGE_INTEGER tgtTicks; + LARGE_INTEGER curTicks; + + QueryPerformanceCounter(&curTicks); + tgtTicks.QuadPart = curTicks.QuadPart + (usec*ticksFreq_)/1000000; + + while (curTicks.QuadPart < tgtTicks.QuadPart) + QueryPerformanceCounter(&curTicks); +#elif defined(Q_OS_LINUX) + struct timeval delay, target, now; + + //qDebug("usec delay = %ld", usec); + + delay.tv_sec = 0; + delay.tv_usec = usec; + + while (delay.tv_usec >= 1000000) + { + delay.tv_sec++; + delay.tv_usec -= 1000000; + } + + gettimeofday(&now, NULL); + timeradd(&now, &delay, &target); + + do { + gettimeofday(&now, NULL); + } while (timercmp(&now, &target, <)); +#else + QThread::usleep(usec); +#endif +} + +PcapPort::PortCapturer::PortCapturer(const char *device) +{ + device_ = QString::fromAscii(device); + stop_ = false; + state_ = kNotStarted; + + if (!capFile_.open()) + qWarning("Unable to open temp cap file"); + + qDebug("cap file = %s", capFile_.fileName().toAscii().constData()); + + dumpHandle_ = NULL; + handle_ = NULL; +} + +PcapPort::PortCapturer::~PortCapturer() +{ + capFile_.close(); +} + +void PcapPort::PortCapturer::run() +{ + int flag = PCAP_OPENFLAG_PROMISCUOUS; + char errbuf[PCAP_ERRBUF_SIZE] = ""; + + qDebug("In %s", __PRETTY_FUNCTION__); + + if (!capFile_.isOpen()) + { + qWarning("temp cap file is not open"); + goto _exit; + } +_retry: + handle_ = pcap_open_live(device_.toAscii().constData(), 65535, + flag, 1000 /* ms */, errbuf); + + if (handle_ == NULL) + { + if (flag && QString(errbuf).contains("promiscuous")) + { + qDebug("%s:can't set promiscuous mode, trying non-promisc", + device_.toAscii().constData()); + flag = 0; + goto _retry; + } + else + { + qDebug("%s: Error opening port %s: %s\n", __FUNCTION__, + device_.toAscii().constData(), errbuf); + goto _exit; + } + } + + dumpHandle_ = pcap_dump_open(handle_, + capFile_.fileName().toAscii().constData()); + state_ = kRunning; + while (1) + { + int ret; + struct pcap_pkthdr *hdr; + const uchar *data; + + ret = pcap_next_ex(handle_, &hdr, &data); + switch (ret) + { + case 1: + pcap_dump((uchar*) dumpHandle_, hdr, data); + break; + case 0: + // timeout: just go back to the loop + break; + case -1: + qWarning("%s: error reading packet (%d): %s", + __PRETTY_FUNCTION__, ret, pcap_geterr(handle_)); + break; + case -2: + default: + qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__, ret); + } + + if (stop_) + { + qDebug("user requested capture stop\n"); + break; + } + } + pcap_dump_close(dumpHandle_); + pcap_close(handle_); + dumpHandle_ = NULL; + handle_ = NULL; + stop_ = false; + +_exit: + state_ = kFinished; +} + +void PcapPort::PortCapturer::start() +{ + // FIXME: return error + if (state_ == kRunning) { + qWarning("Capture start requested but is already running!"); + return; + } + + state_ = kNotStarted; + QThread::start(); + + while (state_ == kNotStarted) + QThread::msleep(10); +} + +void PcapPort::PortCapturer::stop() +{ + if (state_ == kRunning) { + stop_ = true; + while (state_ == kRunning) + QThread::msleep(10); + } + else { + // FIXME: return error + qWarning("Capture stop requested but is not running!"); + return; + } +} + +bool PcapPort::PortCapturer::isRunning() +{ + return (state_ == kRunning); +} + +QFile* PcapPort::PortCapturer::captureFile() +{ + return &capFile_; +} diff --git a/server/pcapport.h b/server/pcapport.h new file mode 100644 index 0000000..b25ab5c --- /dev/null +++ b/server/pcapport.h @@ -0,0 +1,236 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SERVER_PCAP_PORT_H +#define _SERVER_PCAP_PORT_H + +#include +#include +#include + +#include "abstractport.h" +#include "pcapextra.h" + +class PcapPort : public AbstractPort +{ +public: + PcapPort(int id, const char *device); + ~PcapPort(); + + void init(); + + virtual bool hasExclusiveControl() { return false; } + virtual bool setExclusiveControl(bool /*exclusive*/) { return false; } + + virtual void clearPacketList() { + transmitter_->clearPacketList(); + setPacketListLoopMode(false, 0, 0); + } + virtual void loopNextPacketSet(qint64 size, qint64 repeats, + long repeatDelaySec, long repeatDelayNsec) { + transmitter_->loopNextPacketSet(size, repeats, + repeatDelaySec, repeatDelayNsec); + } + virtual bool appendToPacketList(long sec, long nsec, const uchar *packet, + int length) { + return transmitter_->appendToPacketList(sec, nsec, packet, length); + } + virtual void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay) + { + transmitter_->setPacketListLoopMode(loop, secDelay, nsecDelay); + } + + virtual void startTransmit() { + Q_ASSERT(!isDirty()); + transmitter_->start(); + } + virtual void stopTransmit() { transmitter_->stop(); } + virtual bool isTransmitOn() { return transmitter_->isRunning(); } + + virtual void startCapture() { capturer_->start(); } + virtual void stopCapture() { capturer_->stop(); } + virtual bool isCaptureOn() { return capturer_->isRunning(); } + virtual QIODevice* captureData() { return capturer_->captureFile(); } + +protected: + enum Direction + { + kDirectionRx, + kDirectionTx + }; + + class PortMonitor: public QThread + { + public: + PortMonitor(const char *device, Direction direction, + AbstractPort::PortStats *stats); + ~PortMonitor(); + void run(); + void stop(); + pcap_t* handle() { return handle_; } + Direction direction() { return direction_; } + bool isDirectional() { return isDirectional_; } + bool isPromiscuous() { return isPromisc_; } + protected: + AbstractPort::PortStats *stats_; + bool stop_; + private: + pcap_t *handle_; + Direction direction_; + bool isDirectional_; + bool isPromisc_; + }; + + class PortTransmitter: public QThread + { + public: + PortTransmitter(const char *device); + ~PortTransmitter(); + void clearPacketList(); + void loopNextPacketSet(qint64 size, qint64 repeats, + long repeatDelaySec, long repeatDelayNsec); + bool appendToPacketList(long sec, long usec, const uchar *packet, + int length); + void setPacketListLoopMode(bool loop, quint64 secDelay, quint64 nsecDelay) { + returnToQIdx_ = loop ? 0 : -1; + loopDelay_ = secDelay*long(1e6) + nsecDelay/1000; + } + void setHandle(pcap_t *handle); + void useExternalStats(AbstractPort::PortStats *stats); + void run(); + void start(); + void stop(); + bool isRunning(); + private: + enum State + { + kNotStarted, + kRunning, + kFinished + }; + + class PacketSequence + { + public: + PacketSequence() { + sendQueue_ = pcap_sendqueue_alloc(1*1024*1024); + lastPacket_ = NULL; + packets_ = 0; + bytes_ = 0; + usecDuration_ = 0; + repeatCount_ = 1; + repeatSize_ = 1; + usecDelay_ = 0; + } + ~PacketSequence() { + pcap_sendqueue_destroy(sendQueue_); + } + bool hasFreeSpace(int size) { + if ((sendQueue_->len + size) <= sendQueue_->maxlen) + return true; + else + return false; + } + int appendPacket(const struct pcap_pkthdr *pktHeader, + const uchar *pktData) { + if (lastPacket_) + { + usecDuration_ += (pktHeader->ts.tv_sec + - lastPacket_->ts.tv_sec) * long(1e6); + usecDuration_ += (pktHeader->ts.tv_usec + - lastPacket_->ts.tv_usec); + } + packets_++; + bytes_ += pktHeader->caplen; + lastPacket_ = (struct pcap_pkthdr *) + (sendQueue_->buffer + sendQueue_->len); + return pcap_sendqueue_queue(sendQueue_, pktHeader, pktData); + } + pcap_send_queue *sendQueue_; + struct pcap_pkthdr *lastPacket_; + long packets_; + long bytes_; + ulong usecDuration_; + int repeatCount_; + int repeatSize_; + long usecDelay_; + }; + + void udelay(long usec); + int sendQueueTransmit(pcap_t *p, pcap_send_queue *queue, long &overHead, + int sync); + + quint64 ticksFreq_; + QList packetSequenceList_; + PacketSequence *currentPacketSequence_; + int repeatSequenceStart_; + quint64 repeatSize_; + quint64 packetCount_; + + int returnToQIdx_; + quint64 loopDelay_; + + bool usingInternalStats_; + AbstractPort::PortStats *stats_; + bool usingInternalHandle_; + pcap_t *handle_; + volatile bool stop_; + volatile State state_; + }; + + class PortCapturer: public QThread + { + public: + PortCapturer(const char *device); + ~PortCapturer(); + void run(); + void start(); + void stop(); + bool isRunning(); + QFile* captureFile(); + + private: + enum State + { + kNotStarted, + kRunning, + kFinished + }; + + QString device_; + volatile bool stop_; + QTemporaryFile capFile_; + pcap_t *handle_; + pcap_dumper_t *dumpHandle_; + volatile State state_; + }; + + PortMonitor *monitorRx_; + PortMonitor *monitorTx_; + + void updateNotes(); + +private: + PortTransmitter *transmitter_; + PortCapturer *capturer_; + + static pcap_if_t *deviceList_; +}; + +#endif diff --git a/server/portmanager.cpp b/server/portmanager.cpp new file mode 100644 index 0000000..897ad4f --- /dev/null +++ b/server/portmanager.cpp @@ -0,0 +1,144 @@ +/* +Copyright (C) 2010-2012 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "portmanager.h" + +#include "bsdport.h" +#include "linuxport.h" +#include "pcapport.h" +#include "settings.h" +#include "winpcapport.h" + +#include +#include + +PortManager *PortManager::instance_ = NULL; + +PortManager::PortManager() +{ + int i; + pcap_if_t *deviceList; + pcap_if_t *device; + char errbuf[PCAP_ERRBUF_SIZE]; + + qDebug("Retrieving the device list from the local machine\n"); + + if (pcap_findalldevs(&deviceList, errbuf) == -1) + qDebug("Error in pcap_findalldevs_ex: %s\n", errbuf); + + for(device = deviceList, i = 0; device != NULL; device = device->next, i++) + { + AbstractPort *port; + + qDebug("%d. %s", i, device->name); + if (device->description) + qDebug(" (%s)\n", device->description); + +#if defined(Q_OS_WIN32) + if (!filterAcceptsPort(device->description)) +#else + if (!filterAcceptsPort(device->name)) +#endif + { + qDebug("%s (%s) rejected by filter. Skipping!", + device->name, device->description); + i--; + continue; + } + +#if defined(Q_OS_WIN32) + port = new WinPcapPort(i, device->name); +#elif defined(Q_OS_LINUX) + port = new LinuxPort(i, device->name); +#elif defined(Q_OS_BSD4) + port = new BsdPort(i, device->name); +#else + port = new PcapPort(i, device->name); +#endif + + if (!port->isUsable()) + { + qDebug("%s: unable to open %s. Skipping!", __FUNCTION__, + device->name); + delete port; + i--; + continue; + } + + portList_.append(port); + } + + pcap_freealldevs(deviceList); + + foreach(AbstractPort *port, portList_) + port->init(); + + return; +} + +PortManager::~PortManager() +{ + while (!portList_.isEmpty()) + delete portList_.takeFirst(); +} + +PortManager* PortManager::instance() +{ + if (!instance_) + instance_ = new PortManager; + + return instance_; +} + +bool PortManager::filterAcceptsPort(const char *name) +{ + QRegExp pattern; + QStringList includeList = appSettings->value(kPortListIncludeKey) + .toStringList(); + QStringList excludeList = appSettings->value(kPortListExcludeKey) + .toStringList(); + + pattern.setPatternSyntax(QRegExp::Wildcard); + + // An empty (or missing) includeList accepts all ports + // NOTE: A blank "IncludeList=" is read as a stringlist with one + // string which is empty => treat it same as an empty stringlist + if (includeList.isEmpty() + || (includeList.size() == 1 && includeList.at(0).isEmpty())) + goto _include_pass; + + foreach (QString str, includeList) { + pattern.setPattern(str); + if (pattern.exactMatch(name)) + goto _include_pass; + } + + // IncludeList is not empty and port did not match a pattern + return false; + +_include_pass: + foreach (QString str, excludeList) { + pattern.setPattern(str); + if (pattern.exactMatch(name)) + return false; + } + + // Port did not match a pattern in ExcludeList + return true; +} diff --git a/server/portmanager.h b/server/portmanager.h new file mode 100644 index 0000000..801fbf1 --- /dev/null +++ b/server/portmanager.h @@ -0,0 +1,46 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SERVER_PORT_MANAGER_H +#define _SERVER_PORT_MANAGER_H + +#include +#include "abstractport.h" + +class PortManager +{ +public: + PortManager(); + ~PortManager(); + + int portCount() { return portList_.size(); } + AbstractPort* port(int id) { return portList_[id]; } + + static PortManager* instance(); + +private: + bool filterAcceptsPort(const char *name); + +private: + QList portList_; + + static PortManager *instance_; +}; + +#endif diff --git a/server/settings.h b/server/settings.h new file mode 100644 index 0000000..50a3b03 --- /dev/null +++ b/server/settings.h @@ -0,0 +1,34 @@ +/* +Copyright (C) 2014 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SETTINGS_H +#define _SETTINGS_H + +#include +#include + +extern QSettings *appSettings; + +// +// PortList Section Keys +// +const QString kPortListIncludeKey("PortList/Include"); +const QString kPortListExcludeKey("PortList/Exclude"); + +#endif diff --git a/server/winpcapport.cpp b/server/winpcapport.cpp new file mode 100644 index 0000000..f42adaa --- /dev/null +++ b/server/winpcapport.cpp @@ -0,0 +1,226 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#include "winpcapport.h" + +#include +#include + +#ifdef Q_OS_WIN32 + +const uint OID_GEN_MEDIA_CONNECT_STATUS = 0x00010114; + +WinPcapPort::WinPcapPort(int id, const char *device) + : PcapPort(id, device) +{ + monitorRx_->stop(); + monitorTx_->stop(); + monitorRx_->wait(); + monitorTx_->wait(); + + delete monitorRx_; + delete monitorTx_; + + monitorRx_ = new PortMonitor(device, kDirectionRx, &stats_); + monitorTx_ = new PortMonitor(device, kDirectionTx, &stats_); + + adapter_ = PacketOpenAdapter((CHAR*)device); + if (!adapter_) + qFatal("Unable to open adapter %s", device); + linkStateOid_ = (PPACKET_OID_DATA) malloc(sizeof(PACKET_OID_DATA) + + sizeof(uint)); + if (!linkStateOid_) + qFatal("failed to alloc oidData"); + + data_.set_is_exclusive_control(hasExclusiveControl()); + minPacketSetSize_ = 256; +} + +WinPcapPort::~WinPcapPort() +{ +} + +OstProto::LinkState WinPcapPort::linkState() +{ + memset(linkStateOid_, 0, sizeof(PACKET_OID_DATA) + sizeof(uint)); + + linkStateOid_->Oid = OID_GEN_MEDIA_CONNECT_STATUS; + linkStateOid_->Length = sizeof(uint); + + if (PacketRequest(adapter_, 0, linkStateOid_)) + { + uint state; + + if (linkStateOid_->Length == sizeof(state)) + { + memcpy((void*)&state, (void*)linkStateOid_->Data, + linkStateOid_->Length); + if (state == 0) + linkState_ = OstProto::LinkStateUp; + else if (state == 1) + linkState_ = OstProto::LinkStateDown; + } + } + + return linkState_; +} + +bool WinPcapPort::hasExclusiveControl() +{ + QString portName(adapter_->Name + strlen("\\Device\\NPF_")); + QString bindConfigFilePath(QCoreApplication::applicationDirPath() + + "/bindconfig.exe"); + int exitCode; + + qDebug("%s: %s", __FUNCTION__, portName.toAscii().constData()); + + if (!QFile::exists(bindConfigFilePath)) + return false; + + exitCode = QProcess::execute(bindConfigFilePath, + QStringList() << "comp" << portName); + + qDebug("%s: exit code %d", __FUNCTION__, exitCode); + + if (exitCode == 0) + return true; + else + return false; +} + +bool WinPcapPort::setExclusiveControl(bool exclusive) +{ + QString portName(adapter_->Name + strlen("\\Device\\NPF_")); + QString bindConfigFilePath(QCoreApplication::applicationDirPath() + + "/bindconfig.exe"); + QString status; + + qDebug("%s: %s", __FUNCTION__, portName.toAscii().constData()); + + if (!QFile::exists(bindConfigFilePath)) + return false; + + status = exclusive ? "disable" : "enable"; + + QProcess::execute(bindConfigFilePath, + QStringList() << "comp" << portName << status); + + updateNotes(); + + return (exclusive == hasExclusiveControl()); +} + +WinPcapPort::PortMonitor::PortMonitor(const char *device, Direction direction, + AbstractPort::PortStats *stats) + : PcapPort::PortMonitor(device, direction, stats) +{ + if (handle()) + pcap_setmode(handle(), MODE_STAT); +} + +void WinPcapPort::PortMonitor::run() +{ + struct timeval lastTs; + quint64 lastTxPkts = 0; + quint64 lastTxBytes = 0; + + qDebug("in %s", __PRETTY_FUNCTION__); + + lastTs.tv_sec = 0; + lastTs.tv_usec = 0; + + while (!stop_) + { + int ret; + struct pcap_pkthdr *hdr; + const uchar *data; + + ret = pcap_next_ex(handle(), &hdr, &data); + switch (ret) + { + case 1: + { + quint64 pkts = *((quint64*)(data + 0)); + quint64 bytes = *((quint64*)(data + 8)); + + // TODO: is it 12 or 16? + bytes -= pkts * 12; + + uint usec = (hdr->ts.tv_sec - lastTs.tv_sec) * 1000000 + + (hdr->ts.tv_usec - lastTs.tv_usec); + + switch (direction()) + { + case kDirectionRx: + stats_->rxPkts += pkts; + stats_->rxBytes += bytes; + stats_->rxPps = (pkts * 1000000) / usec; + stats_->rxBps = (bytes * 1000000) / usec; + break; + + case kDirectionTx: + if (isDirectional()) + { + stats_->txPkts += pkts; + stats_->txBytes += bytes; + } + else + { + // Assuming stats_->txXXX are updated externally + quint64 txPkts = stats_->txPkts; + quint64 txBytes = stats_->txBytes; + + pkts = txPkts - lastTxPkts; + bytes = txBytes - lastTxBytes; + + lastTxPkts = txPkts; + lastTxBytes = txBytes; + } + stats_->txPps = (pkts * 1000000) / usec; + stats_->txBps = (bytes * 1000000) / usec; + break; + + default: + Q_ASSERT(false); + } + + break; + } + case 0: + //qDebug("%s: timeout. continuing ...", __PRETTY_FUNCTION__); + continue; + case -1: + qWarning("%s: error reading packet (%d): %s", + __PRETTY_FUNCTION__, ret, pcap_geterr(handle())); + break; + case -2: + qWarning("%s: error reading packet (%d): %s", + __PRETTY_FUNCTION__, ret, pcap_geterr(handle())); + break; + default: + qFatal("%s: Unexpected return value %d", __PRETTY_FUNCTION__, ret); + } + lastTs.tv_sec = hdr->ts.tv_sec; + lastTs.tv_usec = hdr->ts.tv_usec; + if (!stop_) + QThread::msleep(1000); + } +} + +#endif diff --git a/server/winpcapport.h b/server/winpcapport.h new file mode 100644 index 0000000..5ab2f9b --- /dev/null +++ b/server/winpcapport.h @@ -0,0 +1,56 @@ +/* +Copyright (C) 2010 Srivats P. + +This file is part of "Ostinato" + +This 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 +*/ + +#ifndef _SERVER_WIN_PCAP_PORT_H +#define _SERVER_WIN_PCAP_PORT_H + +#include + +#ifdef Q_OS_WIN32 + +#include "pcapport.h" + +#include + +class WinPcapPort : public PcapPort +{ +public: + WinPcapPort(int id, const char *device); + ~WinPcapPort(); + + virtual OstProto::LinkState linkState(); + virtual bool hasExclusiveControl(); + virtual bool setExclusiveControl(bool exclusive); + +protected: + class PortMonitor: public PcapPort::PortMonitor + { + public: + PortMonitor(const char *device, Direction direction, + AbstractPort::PortStats *stats); + void run(); + }; +private: + LPADAPTER adapter_; + PPACKET_OID_DATA linkStateOid_ ; +}; + +#endif + +#endif diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000..d5e8af1 --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,109 @@ + +#include "ostprotolib.h" +#include "pcapfileformat.h" +#include "protocol.pb.h" +#include "protocolmanager.h" +#include "settings.h" + +#include +#include +#include +#include + +extern ProtocolManager *OstProtocolManager; + +QSettings *appSettings; + +#if defined(Q_OS_WIN32) +QString kGzipPathDefaultValue; +QString kDiffPathDefaultValue; +QString kAwkPathDefaultValue; +#endif + +int usage(int /*argc*/, char* argv[]) +{ + printf("usage:\n"); + printf("%s \n", argv[0]); + printf("command -\n"); + printf(" importpcap\n"); + + return 255; +} + +int testImportPcap(int argc, char* argv[]) +{ + bool isOk; + QString error; + + if (argc != 3) + { + printf("usage:\n"); + printf("%s importpcap \n", argv[0]); + return 255; + } + + OstProto::StreamConfigList streams; + QString inFile(argv[2]); + + isOk = pcapFileFormat.openStreams(inFile, streams, error); + if (!error.isEmpty()) + { + printf("%s: %s\n", + inFile.toAscii().constData(), error.toAscii().constData()); + } + + if (!isOk) + return 1; + + return 0; +} + +int main(int argc, char* argv[]) +{ + QCoreApplication app(argc, argv); + int exitCode = 0; + + // app init starts ... +#if defined(Q_OS_WIN32) + kGzipPathDefaultValue = app.applicationDirPath() + "/gzip.exe"; + kDiffPathDefaultValue = app.applicationDirPath() + "/diff.exe"; + kAwkPathDefaultValue = app.applicationDirPath() + "/gawk.exe"; +#endif + + app.setApplicationName("Ostinato"); + app.setOrganizationName("Ostinato"); + + OstProtocolManager = new ProtocolManager(); + + /* (Portable Mode) If we have a .ini file in the same directory as the + executable, we use that instead of the platform specific location + and format for the settings */ + QString portableIni = QCoreApplication::applicationDirPath() + + "/ostinato.ini"; + if (QFile::exists(portableIni)) + appSettings = new QSettings(portableIni, QSettings::IniFormat); + else + appSettings = new QSettings(); + + OstProtoLib::setExternalApplicationPaths( + appSettings->value(kTsharkPathKey, kTsharkPathDefaultValue).toString(), + appSettings->value(kGzipPathKey, kGzipPathDefaultValue).toString(), + appSettings->value(kDiffPathKey, kDiffPathDefaultValue).toString(), + appSettings->value(kAwkPathKey, kAwkPathDefaultValue).toString()); + + // ... app init finished + + // + // identify and run specified test + // + if (argc < 2) + exitCode = usage(argc, argv); + else if (strcmp(argv[1],"importpcap") == 0) + exitCode = testImportPcap(argc, argv); + else + exitCode = usage(argc, argv); + + delete appSettings; + return exitCode; +} + diff --git a/test/rpctest.py b/test/rpctest.py new file mode 100644 index 0000000..3ec8ec7 --- /dev/null +++ b/test/rpctest.py @@ -0,0 +1,554 @@ +#! /usr/bin/env python + +# standard modules +import logging +import os +import subprocess +import sys +import time + +sys.path.insert(1, '../binding') +from core import ost_pb, DroneProxy +from rpc import RpcError +from protocols.mac_pb2 import mac +from protocols.ip4_pb2 import ip4, Ip4 + +class Test: + pass + +class TestSuite: + def __init__(self): + self.results = [] + self.total = 0 + self.passed = 0 + self.completed = False + + def test_begin(self, name): + test = Test() + test.name = name + test.passed = False + self.running = test + print('-----------------------------------------------------------') + print('@@TEST: %s' % name) + print('-----------------------------------------------------------') + + def test_end(self, result): + if self.running: + self.running.passed = result + self.results.append(self.running) + self.total = self.total + 1 + if result: + self.passed = self.passed + 1 + self.running = None + print('@@RESULT: %s' % ('PASS' if result else 'FAIL')) + else: + raise Exception('Test end without a test begin') + + def report(self): + print('===========================================================') + print('TEST REPORT') + print('===========================================================') + for test in self.results: + print('%s: %d' % (test.name, test.passed)) + print('Passed: %d/%d' % (self.passed, self.total)) + print('Completed: %d' % (self.completed)) + + def complete(self): + self.completed = True + + def passed(self): + return passed == total and self.completed + +# initialize defaults +host_name = '127.0.0.1' +tx_port_number = -1 +rx_port_number = -1 +drone_version = ['0', '0', '0'] + +if sys.platform == 'win32': + tshark = r'C:\Program Files\Wireshark\tshark.exe' +else: + tshark = 'tshark' + + +# setup logging +log = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + +print('') +print('This test uses the following topology -') +print('') +print(' +-------+ ') +print(' | |Tx--->----+') +print(' | Drone | |') +print(' | |Rx---<----+') +print(' +-------+ ') +print('') +print('A loopback port is used as both the Tx and Rx ports') +print('') + +suite = TestSuite() +drone = DroneProxy(host_name) + +try: + # ----------------------------------------------------------------- # + # TESTCASE: Verify any RPC before checkVersion() fails and the server + # closes the connection + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('anyRpcBeforeCheckVersionFails') + drone.channel.connect(drone.host, drone.port) + try: + port_id_list = drone.getPortIdList() + except RpcError as e: + if ('compatibility check pending' in str(e)): + passed = True + else: + raise + finally: + drone.channel.disconnect() + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify DroneProxy.connect() fails for incompatible version + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('connectFailsForIncompatibleVersion') + try: + drone.proxy_version = '0.1.1' + drone.connect() + except RpcError as e: + if ('needs client version' in str(e)): + passed = True + drone_version = str(e).split()[-1].split('.') + else: + raise + finally: + drone.proxy_version = None + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify checkVersion() fails for invalid client version format + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('checkVersionFailsForInvalidClientVersion') + try: + drone.proxy_version = '0-1-1' + drone.connect() + except RpcError as e: + if ('invalid version' in str(e)): + passed = True + else: + raise + finally: + drone.proxy_version = None + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify checkVersion() returns incompatible if the 'major' + # part of the numbering format is + # different than the server's version and the server closes + # the connection + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('checkVersionReturnsIncompatForDifferentMajorVersion') + try: + drone.proxy_version = (str(int(drone_version[0])+1) + + '.' + drone_version[1]) + drone.connect() + except RpcError as e: + #FIXME: How to check for a closed connection? + if ('needs client version' in str(e)): + passed = True + else: + raise + finally: + drone.proxy_version = None + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify checkVersion() returns incompatible if the 'minor' + # part of the numbering format is + # different than the server's version and the server closes + # the connection + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('checkVersionReturnsIncompatForDifferentMinorVersion') + try: + drone.proxy_version = (drone_version[0] + + '.' + str(int(drone_version[1])+1)) + drone.connect() + except RpcError as e: + #FIXME: How to check for a closed connection? + if ('needs client version' in str(e)): + passed = True + else: + raise + finally: + drone.proxy_version = None + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify checkVersion() returns compatible if the 'revision' + # part of the numbering format is + # different than the server's version + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('checkVersionReturnsCompatForDifferentRevisionVersion') + try: + drone.proxy_version = (drone_version[0] + + '.' + drone_version[1] + + '.' + '999') + drone.connect() + passed = True + except RpcError as e: + raise + finally: + drone.proxy_version = None + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # Baseline Configuration for subsequent testcases + # ----------------------------------------------------------------- # + + # connect to drone + log.info('connecting to drone(%s:%d)' + % (drone.hostName(), drone.portNumber())) + drone.connect() + + # retreive port id list + log.info('retreiving port list') + port_id_list = drone.getPortIdList() + + # retreive port config list + log.info('retreiving port config for all ports') + port_config_list = drone.getPortConfig(port_id_list) + + if len(port_config_list.port) == 0: + log.warning('drone has no ports!') + sys.exit(1) + + # iterate port list to find a loopback port to use as the tx/rx port id + print('Port List') + print('---------') + for port in port_config_list.port: + print('%d.%s (%s)' % (port.port_id.id, port.name, port.description)) + # use a loopback port as default tx/rx port + if ('lo' in port.name or 'loopback' in port.description.lower()): + tx_port_number = port.port_id.id + rx_port_number = port.port_id.id + + if tx_port_number < 0 or rx_port_number < 0: + log.warning('loopback port not found') + sys.exit(1) + + print('Using port %d as tx/rx port(s)' % tx_port_number) + + tx_port = ost_pb.PortIdList() + tx_port.port_id.add().id = tx_port_number; + + rx_port = ost_pb.PortIdList() + rx_port.port_id.add().id = rx_port_number; + + # add a stream + stream_id = ost_pb.StreamIdList() + stream_id.port_id.CopyFrom(tx_port.port_id[0]) + stream_id.stream_id.add().id = 1 + log.info('adding tx_stream %d' % stream_id.stream_id[0].id) + drone.addStream(stream_id) + + # configure the stream + stream_cfg = ost_pb.StreamConfigList() + stream_cfg.port_id.CopyFrom(tx_port.port_id[0]) + s = stream_cfg.stream.add() + s.stream_id.id = stream_id.stream_id[0].id + s.core.is_enabled = True + s.control.num_packets = 10 + + # setup stream protocols as mac:eth2:ip4:udp:payload + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kMacFieldNumber + p.Extensions[mac].dst_mac = 0x001122334455 + p.Extensions[mac].src_mac = 0x00aabbccddee + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kEth2FieldNumber + + p = s.protocol.add() + p.protocol_id.id = ost_pb.Protocol.kIp4FieldNumber + # reduce typing by creating a shorter reference to p.Extensions[ip4] + ip = p.Extensions[ip4] + ip.src_ip = 0x01020304 + ip.dst_ip = 0x05060708 + ip.dst_ip_mode = Ip4.e_im_inc_host + + s.protocol.add().protocol_id.id = ost_pb.Protocol.kUdpFieldNumber + s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + + log.info('configuring tx_stream %d' % stream_id.stream_id[0].id) + drone.modifyStream(stream_cfg) + + # clear tx/rx stats + log.info('clearing tx/rx stats') + drone.clearStats(tx_port) + drone.clearStats(rx_port) + + # ----------------------------------------------------------------- # + # TODO: + # TESTCASE: Verify a RPC with missing required fields in request fails + # and subsequently passes when the fields are initialized + # ----------------------------------------------------------------- # +# passed = False +# suite.test_begin('rpcWithMissingRequiredFieldsFails') +# pid = ost_pb.PortId() +# try: +# sid_list = drone.getStreamIdList(pid) +# except RpcError as e: +# if ('missing required fields in request' in str(e)): +# passed = True +# else: +# raise +# +# try: +# pid.id = tx_port_number +# sid_list = drone.getStreamIdList(pid) +# except RpcError as e: +# passed = False +# raise +# finally: +# suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify invoking addStream() during transmit fails + # TESTCASE: Verify invoking modifyStream() during transmit fails + # TESTCASE: Verify invoking deleteStream() during transmit fails + # ----------------------------------------------------------------- # + sid = ost_pb.StreamIdList() + sid.port_id.CopyFrom(tx_port.port_id[0]) + sid.stream_id.add().id = 2 + + passed = False + suite.test_begin('addStreamDuringTransmitFails') + drone.startTransmit(tx_port) + try: + log.info('adding tx_stream %d' % sid.stream_id[0].id) + drone.addStream(sid) + except RpcError as e: + if ('Port Busy' in str(e)): + passed = True + else: + raise + finally: + drone.stopTransmit(tx_port) + suite.test_end(passed) + + passed = False + suite.test_begin('modifyStreamDuringTransmitFails') + scfg = ost_pb.StreamConfigList() + scfg.port_id.CopyFrom(tx_port.port_id[0]) + s = scfg.stream.add() + s.stream_id.id = sid.stream_id[0].id + s.protocol.add().protocol_id.id = ost_pb.Protocol.kMacFieldNumber + s.protocol.add().protocol_id.id = ost_pb.Protocol.kArpFieldNumber + s.protocol.add().protocol_id.id = ost_pb.Protocol.kPayloadFieldNumber + drone.startTransmit(tx_port) + try: + log.info('configuring tx_stream %d' % sid.stream_id[0].id) + drone.modifyStream(scfg) + except RpcError as e: + if ('Port Busy' in str(e)): + passed = True + else: + raise + finally: + drone.stopTransmit(tx_port) + suite.test_end(passed) + + passed = False + suite.test_begin('deleteStreamDuringTransmitFails') + drone.startTransmit(tx_port) + try: + log.info('deleting tx_stream %d' % sid.stream_id[0].id) + drone.deleteStream(sid) + except RpcError as e: + if ('Port Busy' in str(e)): + passed = True + else: + raise + finally: + drone.stopTransmit(tx_port) + suite.test_end(passed) + + + # ----------------------------------------------------------------- # + # TESTCASE: Verify invoking startTransmit() during transmit is a NOP, + # not a restart + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('startTransmitDuringTransmitIsNopNotRestart') + drone.startCapture(rx_port) + drone.startTransmit(tx_port) + try: + log.info('sleeping for 4s ...') + time.sleep(4) + log.info('starting transmit multiple times') + drone.startTransmit(tx_port) + time.sleep(1) + drone.startTransmit(tx_port) + time.sleep(1) + drone.startTransmit(tx_port) + time.sleep(1) + log.info('waiting for transmit to finish ...') + time.sleep(5) + drone.stopTransmit(tx_port) + drone.stopCapture(rx_port) + + buff = drone.getCaptureBuffer(rx_port.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer') + cap_pkts = subprocess.check_output([tshark, '-r', 'capture.pcap']) + print(cap_pkts) + if '5.6.7.8' in cap_pkts: + passed = True + os.remove('capture.pcap') + except RpcError as e: + raise + finally: + drone.stopTransmit(tx_port) + suite.test_end(passed) + + + # ----------------------------------------------------------------- # + # TESTCASE: Verify invoking startCapture() during capture is a NOP, + # not a restart + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('startCaptureDuringTransmitIsNopNotRestart') + try: + drone.startCapture(rx_port) + drone.startTransmit(tx_port) + log.info('sleeping for 4s ...') + time.sleep(4) + log.info('starting capture multiple times') + drone.startCapture(rx_port) + time.sleep(1) + drone.startCapture(rx_port) + time.sleep(1) + drone.startCapture(rx_port) + time.sleep(1) + log.info('waiting for transmit to finish ...') + time.sleep(5) + drone.stopTransmit(tx_port) + drone.stopCapture(rx_port) + + buff = drone.getCaptureBuffer(rx_port.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer') + cap_pkts = subprocess.check_output([tshark, '-r', 'capture.pcap']) + print(cap_pkts) + if '5.6.7.8' in cap_pkts: + passed = True + os.remove('capture.pcap') + except RpcError as e: + raise + finally: + drone.stopTransmit(tx_port) + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify invoking stopTransmit() when transmit is not running + # is a NOP + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('stopTransmitWhenTransmitNotRunningIsNop') + try: + tx_stats = drone.getStats(tx_port) + log.info('--> (tx_stats)' + tx_stats.__str__()) + if tx_stats.port_stats[0].state.is_transmit_on: + raise Exception('Unexpected transmit ON state') + log.info('stopping transmit multiple times') + drone.stopTransmit(tx_port) + time.sleep(1) + drone.stopTransmit(tx_port) + time.sleep(1) + drone.stopTransmit(tx_port) + + # if we reached here, that means there was no exception + passed = True + except RpcError as e: + raise + finally: + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify invoking stopCapture() when capture is not running + # is a NOP + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('stopCaptureWhenCaptureNotRunningIsNop') + try: + rx_stats = drone.getStats(rx_port) + log.info('--> (rx_stats)' + rx_stats.__str__()) + if rx_stats.port_stats[0].state.is_capture_on: + raise Exception('Unexpected capture ON state') + log.info('stopping capture multiple times') + drone.stopCapture(rx_port) + time.sleep(1) + drone.stopCapture(rx_port) + time.sleep(1) + drone.stopCapture(rx_port) + + # if we reached here, that means there was no exception + passed = True + except RpcError as e: + raise + finally: + suite.test_end(passed) + + # ----------------------------------------------------------------- # + # TESTCASE: Verify startCapture(), startTransmit() sequence captures the + # first packet + # TESTCASE: Verify stopTransmit(), stopCapture() sequence captures the + # last packet + # ----------------------------------------------------------------- # + passed = False + suite.test_begin('startStopTransmitCaptureOrderCapturesAllPackets') + try: + drone.startCapture(rx_port) + drone.startTransmit(tx_port) + log.info('waiting for transmit to finish ...') + time.sleep(12) + drone.stopTransmit(tx_port) + drone.stopCapture(rx_port) + + log.info('getting Rx capture buffer') + buff = drone.getCaptureBuffer(rx_port.port_id[0]) + drone.saveCaptureBuffer(buff, 'capture.pcap') + log.info('dumping Rx capture buffer') + cap_pkts = subprocess.check_output([tshark, '-r', 'capture.pcap']) + print(cap_pkts) + if '5.6.7.8' in cap_pkts and '5.6.7.17' in cap_pkts: + passed = True + os.remove('capture.pcap') + except RpcError as e: + raise + finally: + drone.stopTransmit(tx_port) + suite.test_end(passed) + + suite.complete() + + # delete streams + log.info('deleting tx_stream %d' % stream_id.stream_id[0].id) + drone.deleteStream(stream_id) + + # bye for now + drone.disconnect() + +except Exception as ex: + log.exception(ex) + +finally: + suite.report() + if not suite.passed: + sys.exit(2); diff --git a/test/test.pro b/test/test.pro new file mode 100644 index 0000000..e8542e4 --- /dev/null +++ b/test/test.pro @@ -0,0 +1,39 @@ +TEMPLATE = app +CONFIG += qt console +QT += xml network script +INCLUDEPATH += "../rpc/" "../common/" "../client" +win32 { + LIBS += -lwpcap -lpacket + CONFIG(debug, debug|release) { + LIBS += -L"../common/debug" -lostprotogui -lostproto + LIBS += -L"../rpc/debug" -lpbrpc + POST_TARGETDEPS += \ + "../common/debug/libostprotogui.a" \ + "../common/debug/libostproto.a" \ + "../rpc/debug/libpbrpc.a" + } else { + LIBS += -L"../common/release" -lostprotogui -lostproto + LIBS += -L"../rpc/release" -lpbrpc + POST_TARGETDEPS += \ + "../common/release/libostprotogui.a" \ + "../common/release/libostproto.a" \ + "../rpc/release/libpbrpc.a" + } +} else { + LIBS += -lpcap + LIBS += -L"../common" -lostprotogui -lostproto + LIBS += -L"../rpc" -lpbrpc + POST_TARGETDEPS += \ + "../common/libostprotogui.a" \ + "../common/libostproto.a" \ + "../rpc/libpbrpc.a" +} +LIBS += -lprotobuf +LIBS += -L"../extra/qhexedit2/$(OBJECTS_DIR)/" -lqhexedit2 + +HEADERS += +SOURCES += main.cpp + +QMAKE_DISTCLEAN += object_script.* + +include(../install.pri) diff --git a/version.pri b/version.pri new file mode 100644 index 0000000..97b3673 --- /dev/null +++ b/version.pri @@ -0,0 +1,42 @@ +APP_VERSION = 0.6 +APP_REVISION = $(shell hg identify -i) +#uncomment the below line in a source package and fill-in the correct revision +#APP_REVISION = @ + +ver_info { + APP_VERSION_FILE = version.cpp + revtarget.target = $$APP_VERSION_FILE + win32:revtarget.commands = echo "const char *version = \"$$APP_VERSION\";" \ + "const char *revision = \"$$APP_REVISION\";" \ + > $$APP_VERSION_FILE + unix:revtarget.commands = echo \ + "\"const char *version = \\\"$$APP_VERSION\\\";" \ + "const char *revision = \\\"$$APP_REVISION\\\";\"" \ + > $$APP_VERSION_FILE + revtarget.depends = $$SOURCES $$HEADERS $$FORMS $$POST_TARGETDEPS + + SOURCES += $$APP_VERSION_FILE + QMAKE_EXTRA_TARGETS += revtarget + POST_TARGETDEPS += $$APP_VERSION_FILE + QMAKE_DISTCLEAN += $$APP_VERSION_FILE +} + +pkg_info { + PKG_INFO_FILE = pkg_info.json + pkginfo.target = $$PKG_INFO_FILE + pkginfo.CONFIG = recursive + win32:pkginfo.commands = echo "{" \ + " \"version\": \"$$APP_VERSION\"," \ + " \"revision\": \"$$APP_REVISION\"" \ + "}" \ + > $$PKG_INFO_FILE + unix:pkginfo.commands = echo "\"{" \ + " \\\"version\\\": \\\"$$APP_VERSION\\\"," \ + " \\\"revision\\\": \\\"$$APP_REVISION\\\"" \ + "}\"" \ + > $$PKG_INFO_FILE + + QMAKE_EXTRA_TARGETS += pkginfo + POST_TARGETDEPS += $$PKG_INFO_FILE + QMAKE_DISTCLEAN += $$PKG_INFO_FILE +}