diff --git a/.travis.yml b/.travis.yml index 3e0a1b8b..fee8813b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ go: - 1.7 install: - - ./install.sh + - go get -t ./... - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 00000000..b369333b --- /dev/null +++ b/AUTHORS @@ -0,0 +1,19 @@ +Authors sorted by their family-name. + +# Directly affiliated with EPFL/DEDIS + +Patricia Egger +Bryan Ford +David Froelicher +Nikitin Kirill +Kokoris Kogias Eleftherios +Ismail Khoffi +Nicolas Gailly +Linus Gasser +Philipp Jovanovic + +# NON-EPFL/DEDIS - signed a CLA / CLAI: + +Christian Mouchet +Evan Visher +Mahdi Zamani diff --git a/CLAC b/CLAC new file mode 100644 index 00000000..4b87ba28 --- /dev/null +++ b/CLAC @@ -0,0 +1,157 @@ + EPFL / DEDIS / Lausanne / CH + Software Grant and Corporate Contributor License Agreement ("Agreement") + http://www.apache.org/licenses/ + + Thank you for your interest in software from EPFL/DEDIS (Lab). + In order to clarify the intellectual property license + granted with Contributions from any person or entity, the Lab + must have a Contributor License Agreement (CLA) on file that has been + signed by each Contributor, indicating agreement to the license terms + below. This license is for your protection as a Contributor as well + as the protection of the Lab and its users; it does not change + your rights to use your own Contributions for any other purpose. + + This version of the Agreement allows an entity (the "Corporation") to + submit Contributions to the Lab, to authorize Contributions + submitted by its designated employees to the Lab, and to grant + copyright and patent licenses thereto. + + If you have not already done so, please complete and sign, then scan and + email a pdf file of this Agreement to bryan.ford@epfl.ch. + Alternatively, you may send it by facsimile to the Lab at + +41 21 693 66 10. If necessary, send an original signed Agreement to + EPFL-IC-DEDIS, BC Building, Station 14, CH-1015 Lausanne, + SWITZERLAND. + Please read this document carefully before signing and keep a copy for + your records. + + Corporation name: ________________________________________________ + + Corporation address: ________________________________________________ + + ________________________________________________ + + ________________________________________________ + + Point of Contact: ________________________________________________ + + E-Mail: ________________________________________________ + + Telephone: _____________________ Fax: _____________________ + + + You accept and agree to the following terms and conditions for Your + present and future Contributions submitted to the Lab. In + return, the Lab shall not use Your Contributions in a way that + is contrary to the public benefit or inconsistent with its nonprofit + status and bylaws in effect at the time of the Contribution. Except + for the license granted herein to the Lab and recipients of + software distributed by the Lab, You reserve all right, title, + and interest in and to Your Contributions. + + 1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with the Lab. For legal entities, the entity making a + Contribution and all other entities that control, are controlled by, + or are under common control with that entity are considered to be a + single Contributor. For the purposes of this definition, "control" + means (i) the power, direct or indirect, to cause the direction or + management of such entity, whether by contract or otherwise, or + (ii) ownership of fifty percent (50%) or more of the outstanding + shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean the code, documentation or other original + works of authorship expressly identified in Schedule B, as well as + any original work of authorship, including + any modifications or additions to an existing work, that is intentionally + submitted by You to the Lab for inclusion in, or + documentation of, any of the products owned or managed by the + Lab (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to the Lab or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems + that are managed by, or on behalf of, the Lab for the + purpose of discussing and improving the Work, but excluding + communication that is conspicuously marked or otherwise designated + in writing by You as "Not a Contribution." + + 2. Grant of Copyright License. Subject to the terms and conditions + of this Agreement, You hereby grant to the Lab and to + recipients of software distributed by the Lab a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute + Your Contributions and such derivative works. + + 3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to the Lab and to recipients + of software distributed by the Lab a perpetual, worldwide, + non-exclusive, no-charge, royalty-free, irrevocable (except as + stated in this section) patent license to make, have made, use, + offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by You that are necessarily infringed by Your Contribution(s) + alone or by combination of Your Contribution(s) with the Work to + which such Contribution(s) were submitted. If any entity institutes + patent litigation against You or any other entity (including a + cross-claim or counterclaim in a lawsuit) alleging that your + Contribution, or the Work to which you have contributed, constitutes + direct or contributory patent infringement, then any patent licenses + granted to that entity under this Agreement for that Contribution or + Work shall terminate as of the date such litigation is filed. + + 4. You represent that You are legally entitled to grant the above + license. You represent further that each employee of the + Corporation designated on Schedule A below (or in a subsequent + written modification to that Schedule) is authorized to submit + Contributions on behalf of the Corporation. + + 5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). + + 6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, + MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + + 7. Should You wish to submit work that is not Your original creation, + You may submit it to the Lab separately from any + Contribution, identifying the complete details of its source and + of any license or other restriction (including, but not limited + to, related patents, trademarks, and license agreements) of which + you are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + + 8. It is your responsibility to notify the Lab when any change + is required to the list of designated employees authorized to submit + Contributions on behalf of the Corporation, or to the Corporation's + Point of Contact with the Lab. + + + + Please sign: __________________________________ Date: _______________ + + Title: __________________________________ + + Corporation: __________________________________ + + +Schedule A + + [Initial list of designated employees. NB: authorization is not + tied to particular Contributions.] + + + + +Schedule B + + [Identification of optional concurrent software grant. Would be + left blank or omitted if there is no concurrent software grant.] diff --git a/CLAI b/CLAI new file mode 100644 index 00000000..d3a203dc --- /dev/null +++ b/CLAI @@ -0,0 +1,136 @@ + EPFL / DEDIS / Lausanne / CH + Individual Contributor License Agreement ("Agreement") V2.0 + http://www.apache.org/licenses/ + +Thank you for your interest in software from EPFL/DEDIS (Lab). +In order to clarify the intellectual property license +granted with Contributions from any person or entity, the Lab +must have a Contributor License Agreement ("CLA") on file that has +been signed by each Contributor, indicating agreement to the license +terms below. This license is for your protection as a Contributor as +well as the protection of the Lab and its users; it does not +change your rights to use your own Contributions for any other purpose. +If you have not already done so, please complete and sign, then scan +and email a pdf file of this Agreement to bryan.ford@epfl.ch. +Alternatively, you may send it by facsimile to the Lab at ++41 21 693 66 10. If necessary, send an original signed Agreement to +EPFL-IC-DEDIS, BC Building, Station 14, CH-1015 Lausanne, +SWITZERLAND. Please read this document carefully before +signing and keep a copy for your records. + + Full name: ______________________________________________________ + + (optional) Public name: _________________________________________ + + Mailing Address: ________________________________________________ + + ________________________________________________ + + Country: ______________________________________________________ + + Telephone: ______________________________________________________ + + E-Mail: ______________________________________________________ + + Github account: _________________________________________________ + +You accept and agree to the following terms and conditions for Your +present and future Contributions submitted to the Lab. In +return, the Lab shall not use Your Contributions in a way that +is contrary to the public benefit or inconsistent with its nonprofit +status and bylaws in effect at the time of the Contribution. Except +for the license granted herein to the Lab and recipients of +software distributed by the Lab, You reserve all right, title, +and interest in and to Your Contributions. + +1. Definitions. + + "You" (or "Your") shall mean the copyright owner or legal entity + authorized by the copyright owner that is making this Agreement + with the Lab. For legal entities, the entity making a + Contribution and all other entities that control, are controlled + by, or are under common control with that entity are considered to + be a single Contributor. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "Contribution" shall mean any original work of authorship, + including any modifications or additions to an existing work, that + is intentionally submitted by You to the Lab for inclusion + in, or documentation of, any of the products owned or managed by + the Lab (the "Work"). For the purposes of this definition, + "submitted" means any form of electronic, verbal, or written + communication sent to the Lab or its representatives, + including but not limited to communication on electronic mailing + lists, source code control systems, and issue tracking systems that + are managed by, or on behalf of, the Lab for the purpose of + discussing and improving the Work, but excluding communication that + is conspicuously marked or otherwise designated in writing by You + as "Not a Contribution." + +2. Grant of Copyright License. Subject to the terms and conditions of + this Agreement, You hereby grant to the Lab and to + recipients of software distributed by the Lab a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare derivative works of, + publicly display, publicly perform, sublicense, and distribute Your + Contributions and such derivative works. + +3. Grant of Patent License. Subject to the terms and conditions of + this Agreement, You hereby grant to the Lab and to + recipients of software distributed by the Lab a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have + made, use, offer to sell, sell, import, and otherwise transfer the + Work, where such license applies only to those patent claims + licensable by You that are necessarily infringed by Your + Contribution(s) alone or by combination of Your Contribution(s) + with the Work to which such Contribution(s) was submitted. If any + entity institutes patent litigation against You or any other entity + (including a cross-claim or counterclaim in a lawsuit) alleging + that your Contribution, or the Work to which you have contributed, + constitutes direct or contributory patent infringement, then any + patent licenses granted to that entity under this Agreement for + that Contribution or Work shall terminate as of the date such + litigation is filed. + +4. You represent that you are legally entitled to grant the above + license. If your employer(s) has rights to intellectual property + that you create that includes your Contributions, you represent + that you have received permission to make Contributions on behalf + of that employer, that your employer has waived such rights for + your Contributions to the Lab, or that your employer has + executed a separate Corporate CLA with the Lab. + +5. You represent that each of Your Contributions is Your original + creation (see section 7 for submissions on behalf of others). You + represent that Your Contribution submissions include complete + details of any third-party license or other restriction (including, + but not limited to, related patents and trademarks) of which you + are personally aware and which are associated with any part of Your + Contributions. + +6. You are not expected to provide support for Your Contributions, + except to the extent You desire to provide support. You may provide + support for free, for a fee, or not at all. Unless required by + applicable law or agreed to in writing, You provide Your + Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + OF ANY KIND, either express or implied, including, without + limitation, any warranties or conditions of TITLE, NON- + INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. + +7. Should You wish to submit work that is not Your original creation, + You may submit it to the Lab separately from any + Contribution, identifying the complete details of its source and of + any license or other restriction (including, but not limited to, + related patents, trademarks, and license agreements) of which you + are personally aware, and conspicuously marking the work as + "Submitted on behalf of a third-party: [named here]". + +8. You agree to notify the Lab of any facts or circumstances of + which you become aware that would make these representations + inaccurate in any respect. + +Please sign: __________________________________ Date: ________________ diff --git a/CONTRIBUTION b/CONTRIBUTION new file mode 100644 index 00000000..2dbaf123 --- /dev/null +++ b/CONTRIBUTION @@ -0,0 +1,26 @@ +Making Contributions + +Cothority is an Open Source program with many contributors (listed in the AUTHORS +files), so we actively seek contributions (help on the Cothority users list, +documentation, source code, ideas, …). All contributions help us make a +better product. + +All contributions without an explicit copyright statement or a CLAI/CLAC (see below) +are assumed to be covered under a AGPL 2-Clause license as described in the +file LICENSE. + +Developers who have contributed significant changes to the Cothority code must sign +a Contributor License Agreement (CLAI), eventually also a CLAC if they work for +a corporation. Together they guarantee them the right to use the code they have +developed, and also ensures that EPFL/DEDIS (and thus the Cothority project) has the +rights to the code. +By signing the CLAI/CLAC, your contributions are eligible to be integrated into the +Cothority source code. +Having the CLAI/CLAC signed is essential for the Cothority project to maintain a +clean copyright and guarantees that Cothority and your source code will always remain +Free Software (Open Source). +Providing that your contribution is accepted by the Cothority project, your signed +CLAI/CLAC also permits EPFL/DEDIS to submit your contribution for use in other +EPFL/DEDIS-projects. + +The Contributor License Agreements are in the CLAI and CLAC-file. \ No newline at end of file diff --git a/LICENSE b/LICENSE index 8cdb8451..e876405f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,340 +1,7 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 +Copyright 2016 by EPFL/DEDIS. All rights reserved. - Copyright (C) 1989, 1991 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. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) 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 -this service 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 make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. 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. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -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 Program or any portion -of it, thus forming a work based on the Program, 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) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -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 Program, 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 Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) 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; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, 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 executable. However, as a -special exception, the source code 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. - -If distribution of executable or 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 counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program 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. - - 5. 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 Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program 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 to -this License. - - 7. 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 Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program 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 Program. - -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. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program 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. - - 9. The Free Software Foundation may publish revised and/or new versions -of the 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 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 Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, 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 - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. - - 12. 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 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. - - 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 -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. - - {description} - Copyright (C) {year} {fullname} - - 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 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; 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. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision 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, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - {signature of Ty Coon}, 1 April 1989 - Ty Coon, President of Vice - -This 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. +This project and all its files are licensed under +GNU AGPLv3 or later version. +If this license is not suitable for your business or project +please contact us for a full commercial license. \ No newline at end of file diff --git a/LICENSE.AGPL b/LICENSE.AGPL new file mode 100644 index 00000000..2def0e88 --- /dev/null +++ b/LICENSE.AGPL @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 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 Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are 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. + + 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. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + 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 Affero 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. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + 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 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 work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero 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 Affero 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 Affero 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 Affero 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 Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + 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 AGPL, see +. \ No newline at end of file diff --git a/README.md b/README.md index 64554d0f..9e8ed0ee 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,7 @@ from time to time, as all dedis-dependencies change quite often. There are three apps available: * [cothorityd](https://github.com/dedis/cothority/app/cothorityd) - which is the server-part that you can run to add a node -* [cosi](https://github.com/dedis/cosi) - the cosi-protocol, service, and app, -in its own repository +* [CoSi](https://github.com/dedis/cothority/app/cosi) - the CoSi-app * [status](https://github.com/dedis/cothority/app/status) - reads out the status of a cothority You will find a README.md in each of its directory. To build the apps, you can @@ -75,14 +74,13 @@ run the following commands: ``` go get github.com/dedis/cothority/app/cothorityd -go get github.com/dedis/cosi go get github.com/dedis/cothority/app/status ``` # Apps * [cothorityd](app/cothorityd) - the basic -* [cosi](https://github.com/dedis/cosi) - collective signatures +* [cosi](app/cosi) - collective signatures * [status](app/status) - returns the status of the given group * [cisc](app/cisc) - handle your ssh-keys on a blockchain * [hotpets](https://github.com/dedis/cothority/tree/hpets16/app/cisc) - hotpets16-branch diff --git a/app/cisc/cisc.go b/app/cisc/cisc.go index d0e20e04..3631ab33 100644 --- a/app/cisc/cisc.go +++ b/app/cisc/cisc.go @@ -22,7 +22,7 @@ import ( "github.com/dedis/cothority/app/lib/config" "github.com/dedis/cothority/log" "github.com/dedis/cothority/services/identity" - "gopkg.in/codegangsta/cli.v1" + "gopkg.in/urfave/cli.v1" ) func main() { diff --git a/app/cisc/commands.go b/app/cisc/commands.go index d56677e3..4ad82412 100644 --- a/app/cisc/commands.go +++ b/app/cisc/commands.go @@ -1,6 +1,6 @@ package main -import "gopkg.in/codegangsta/cli.v1" +import "gopkg.in/urfave/cli.v1" /* This holds the cli-commands so the main-file is less cluttered. diff --git a/app/cisc/lib.go b/app/cisc/lib.go index ee3383aa..b11f6e28 100644 --- a/app/cisc/lib.go +++ b/app/cisc/lib.go @@ -18,7 +18,7 @@ import ( "github.com/dedis/cothority/network" "github.com/dedis/cothority/sda" "github.com/dedis/cothority/services/identity" - "gopkg.in/codegangsta/cli.v1" + "gopkg.in/urfave/cli.v1" ) func init() { diff --git a/app/cosi/README.md b/app/cosi/README.md new file mode 100644 index 00000000..63df8a60 --- /dev/null +++ b/app/cosi/README.md @@ -0,0 +1,316 @@ +[![Build Status](https://travis-ci.org/dedis/cothority.svg?branch=master)](https://travis-ci.org/dedis/cothority) +[![Coverage Status](https://coveralls.io/repos/github/dedis/cothority/badge.svg?branch=master)](https://coveralls.io/github/dedis/costhority?branch=master) + + +# CoSi + +This package implements a standalone version of the CoSi protocol for +scalable collective signing. +CoSi enables authorities to have their statements collectively signed, +or *co-signed*, by a scalable group of independent parties or *witnesses*. +The signatures that CoSi produces convey the same information +as a list of conventional signatures from all participants, +but CoSi signatures are far more compact and efficient for clients to verify. +In practice, a CoSi collective signature is close to the same size as +-and comparable in verification costs to- +a *single* individual signature. + +CoSi is intended to facilitate increased transparency and security-hardening +for critical Internet authorities such as certificate authorities, +[time services](http://www.nist.gov/pml/div688/grp40/its.cfm), +naming authorities such as [DNSSEC](http://www.dnssec.net), +software distribution and update services, +directory services used by tools such as [Tor](https://www.torproject.org), +and next-generation crypto-currencies. +For further background and technical details see this research paper: +* [Keeping Authorities "Honest or Bust" with Decentralized Witness Cosigning](http://dedis.cs.yale.edu/dissent/papers/witness-abs), +[IEEE Security & Privacy 2016](http://www.ieee-security.org/TC/SP2016/). + +For questions and discussions please join the +[mailing list](https://groups.google.com/forum/#!forum/cothority). + +Other related papers: +* [Certificate Cothority - Towards Trustworthy Collective CAs](https://petsymposium.org/2015/papers/syta-cc-hotpets2015.pdf), +[HotPETS 2015](https://petsymposium.org/2015/hotpets.php) +* [Enhancing Bitcoin Security and Performance with Strong Consistency via Collective Signing](http://arxiv.org/abs/1602.06997), +[USENIX Security 2016](https://www.usenix.org/conference/usenixsecurity16) (to appear) + + +**Warning: This software is experimental and still under development. +Do not use it yet for security-critical purposes. If you use it, +do so to supplement, rather than replace, existing signing mechanisms. +Use at your own risk!** + +# Versions + +For the moment we have two version: _v0_ +(in the [cosi repo](https://github.com/dedis/cosi/)) and +_master_ in this repo. + +## V0 + +**Update**: Soon to be deprecated. + +This is a stable version that depends on the v0-versions of the other dedis-packages. +It will only receive bugfixes, but no changes that will make the code incompatible. +You can find this version at: + +https://github.com/dedis/cosi/tree/v0 + +If you write code that uses our library in the v0-version, be sure to reference it as + +``` +import "gopkg.in/dedis/cosi.v0" +``` + +## Master + +The master-branch is used for day-to-day development and will break your +code about once a week. If you are using this branch, be sure to do + +``` +go get -u -t ./... +``` + +from time to time, as all dedis-dependencies change quite often. + +# Installation + +You may install CoSi from either pre-built binaries +or from [Go](https://golang.org/) source code, +as described below. + +## Installing from Source + +To build and run CoSi from source code you will need to install +[Go](https://golang.org/) version 1.7 or later. +See +[the Go documentation](https://golang.org/doc/install) +on how to install and configure Go, +and make sure that +[`$GOPATH` and `$GOBIN` are set](https://golang.org/doc/code.html#GOPATH). +Then you can fetch, update, compile and install the cosi-binary using: + +```bash +go get -u github.com/dedis/cothority/app/cosi +``` + +The `cosi` binary will be installed in the directory indicated by `$GOBIN`. + +# Command-line Interface + +The `cosi` application provides both a client for signing messages, +and a server implementing the cosigner or witness-server role +in the CoSi protocol. + +## Chose the Right Directory for the Examples + +For the examples in the following sections, we suppose you're in a directory +where you can find the following files: `README.md` and `dedis-group.toml`. + +If you compiled from source, please change the directory like so: + +```bash +cd $GOPATH/src/github.com/dedis/cothority/app/cosi/ +``` + +## Collectively Signing Messages with the CoSi Client + +In order to sign messages collectively, you first need to define the set of +cosigners that will participate. To do this, you need to prepare a *group definition* +file which lists the cosigners to use with their public keys and Internet addresses. +You may use [our default list of public CoSi +servers](https://github.com/dedis/cothority/blob/master/app/dedis-servers.toml) if you wish, or define your own. + +CoSi will by default search for a file "group.toml" in the default configuration folders +which are `$HOME/.config/cosi/` for Linux systems and `$HOME/Library/cosi/` for +mac systems. If CoSi did not find anything, the default mechanism is to search in the current +directory. + +Once you have a valid group definition, you can sign a file using the `cosi sign` +command. Here is an example that uses our default public CoSi server group to +sign the README.md file at the top of the cosi source directory: + +```bash +cosi sign -g dedis_group.toml README.md +``` + +When collective signing completes, +the resulting signature will be written to standard output by default. +To write the signature written to a file, +you may redirect output or use the the `-o` option: + +```base +cosi sign -g dedis_group.toml -o README.sig README.md +``` + +To verify a collective signature, use the `cosi verify` command: + +```bash +cosi verify -g dedis_group.toml -s README.sig README.md +``` + +Verification can also take the signature from standard input: + +```bash +cat README.sig | cosi verify -g dedis_group.toml README.md +``` + +In the current prototype, CoSi witness servers do not validate or check the +messages you propose in any way; they merely serve to provide transparency +by publicly attesting the fact that they have observed and cosigned the message. +A future CoSi release will add support for message validation plugins, +by which the servers can apply application-specific checks to messages +before signing off on them, +e.g., to validate a [collectively signed blockchain](http://arxiv.org/abs/1602.06997). + +## Running Your Own CoSi Witness Server + +First you need to create a configuration file for the server including a +public/private key pair. +You can create a default server configuration with a fresh +public/private key pair as follows: + +```bash +cosi server setup +``` + +Follow the instructions on the screen. At the end, you should have two files: +* One local server configuration file which is used by your cothority server, +* One group definition file that you will share with other cothority members and + clients that wants to contact you. + +To run the server, simply type: +```bash +cosi server +``` + +### Custom Configuration Path + +The server will try to read the default configuration file; if you have put the +file in a custom location, provide the path using: +```bash +cosi server -config path/file.toml +``` + +### Debugging Output + +You can also ask the server to print out some debugging messages by indicating +a level. Using level 1 shows when a message gets signed: + +```bash +cosi -d 1 server +``` + +## Creating a Collective Signing Group + +If you run several CoSi servers, +you can concatenate their individual `group.toml` outputs +to define your own cosigning group. +You may optionally use any or all of our experimental +[default CoSi servers](https://github.com/dedis/cothority/blob/master/app/dedis-servers.toml) +if you wish. +Your resulting `group.toml' file should look something like this: + +``` +Description = "My Test group" + +[[servers]] + Addresses = ["127.0.0.1:2000"] + Public = "6T7FwlCuVixvu7XMI9gRPmyCuqxKk/WUaGbwVvhA+kc=" + Description = "Local Server 1" + +[[servers]] + Addresses = ["127.0.0.1:2001"] + Public = "Aq0mVAeAvBZBxQPC9EbI8w6he2FHlz83D+Pz+zZTmJI=" + Description = "Local Server 2" +``` + +Your specific list will be different, of course, +especially in the specific IP addresses and public keys. +If you run multiple servers on the same machine for experimentation, +they must of course be assigned different ports, +e.g., 2000 and 2001 in the example above. + +## Checking the Status of a Cosigning Group + +You may use the `cosi check` command to +verify the availability and operation +of the servers listed in a group definition file: + +```bash +cosi check -g dedis_group.toml +``` + +This will first contact each server individually, then make a small cothority- +group of all possible pairs of servers. +If there are connectivity problems, +due to firewalls or bad connections for example, +you will see a "Timeout on signing" or similar error message. + +### Publicly available DeDiS-CoSi-servers + +For the moment there are four publicly available signing-servers, without +any guarantee that they'll be running. But you can try the following: + +```bash +cat > servers.toml <> ~/.profile; \ echo 'export PATH=$PATH:/usr/lib/go-1.6/bin:~/bin:$GOPATH/bin' >> ~/.bashrc; \ . ~/.profile; \ mkdir go; \ - go get -v github.com/dedis/cosi; \ go get -v github.com/dedis/cothority EXPOSE 2000 diff --git a/app/guard/guard.go b/app/guard/guard.go index ed4364bd..ed538813 100644 --- a/app/guard/guard.go +++ b/app/guard/guard.go @@ -24,7 +24,7 @@ import ( "io/ioutil" s "github.com/SSSaaS/sssa-golang" - "gopkg.in/codegangsta/cli.v1" + "gopkg.in/urfave/cli.v1" "bytes" diff --git a/app/lib/config/config.go b/app/lib/config/config.go index e061a88f..d635ceb4 100644 --- a/app/lib/config/config.go +++ b/app/lib/config/config.go @@ -17,7 +17,6 @@ import ( "github.com/dedis/cothority/network" "github.com/dedis/cothority/sda" "github.com/dedis/crypto/abstract" - "github.com/dedis/crypto/config" ) var in *bufio.Reader @@ -30,9 +29,10 @@ func init() { // CothoritydConfig is the configuration structure of the cothority daemon. type CothoritydConfig struct { - Public string - Private string - Address network.Address + Public string + Private string + Address network.Address + Description string } // Save will save this CothoritydConfig to the given file name. It @@ -68,52 +68,12 @@ func ParseCothorityd(file string) (*CothoritydConfig, *sda.Conode, error) { if err != nil { return nil, nil, err } - conode := sda.NewConodeTCP(network.NewServerIdentity(point, hc.Address), secret) + si := network.NewServerIdentity(point, hc.Address) + si.Description = hc.Description + conode := sda.NewConodeTCP(si, secret) return hc, conode, nil } -// CreateCothoritydConfig uses stdin to get the address. Then it creates -// a private/public key pair. -// It takes the default config file as argument, and returns the -// CothoritydConfig created, the config file name, and any error if occured. -func CreateCothoritydConfig(defaultFile string) (*CothoritydConfig, string, error) { - reader := bufio.NewReader(os.Stdin) - var err error - var str string - // IP:PORT - fmt.Println("[+] Type the IP:PORT (ipv4) address of this host (accessible from Internet):") - str, err = reader.ReadString('\n') - str = strings.TrimSpace(str) - address := network.NewTCPAddress(str) - if !address.Valid() { - return nil, "", fmt.Errorf("Invalid address: %s", address) - } - - fmt.Println("[+] Creation of the private and public keys...") - kp := config.NewKeyPair(network.Suite) - privStr, err := crypto.ScalarHex(network.Suite, kp.Secret) - if err != nil { - return nil, "", fmt.Errorf("Could not parse private key. Abort.") - } - pubStr, err := crypto.PubHex(network.Suite, kp.Public) - if err != nil { - return nil, "", fmt.Errorf("Could not parse public key. Abort.") - } - fmt.Println("\tPrivate:\t", privStr) - fmt.Println("\tPublic: \t", pubStr) - - fmt.Println("[+] Name of the config file [", defaultFile, "]:") - fname, err := reader.ReadString('\n') - fname = strings.TrimSpace(fname) - - config := &CothoritydConfig{ - Public: pubStr, - Private: privStr, - Address: address, - } - return config, fname, err -} - // GroupToml holds the data of the group.toml file. type GroupToml struct { Description string @@ -229,15 +189,17 @@ func (s *ServerToml) toServerIdentity(suite abstract.Suite) (*network.ServerIden // the corresponding ServerToml. // If an error occurs, it will be printed to StdErr and nil // is returned. -func NewServerToml(suite abstract.Suite, public abstract.Point, addr network.Address) *ServerToml { +func NewServerToml(suite abstract.Suite, public abstract.Point, addr network.Address, + desc string) *ServerToml { var buff bytes.Buffer if err := crypto.WritePub64(suite, &buff, public); err != nil { log.Error("Error writing public key") return nil } return &ServerToml{ - Address: addr, - Public: buff.String(), + Address: addr, + Public: buff.String(), + Description: desc, } } diff --git a/app/lib/server/server.go b/app/lib/server/server.go index bb053401..6c2823e4 100644 --- a/app/lib/server/server.go +++ b/app/lib/server/server.go @@ -27,9 +27,9 @@ import ( "github.com/dedis/crypto/cosi" // Empty imports to have the init-functions called which should // register the protocol. - _ "github.com/dedis/cosi/protocol" + _ "github.com/dedis/cothority/protocols/cosi" // For the moment, the server only serves CoSi requests - s "github.com/dedis/cosi/service" + s "github.com/dedis/cothority/services/cosi" "github.com/dedis/crypto/abstract" crypconf "github.com/dedis/crypto/config" ) @@ -51,7 +51,7 @@ const DefaultAddress = "127.0.0.1" const whatsMyIP = "http://www.whatsmyip.org/" // RequestTimeOut is how long we're willing to wait for a signature. -var RequestTimeOut = time.Second * 1 +var RequestTimeOut = time.Second * 10 // InteractiveConfig uses stdin to get the [address:]PORT of the server. // If no address is given, whatsMyIP is used to find the public IP. In case @@ -147,12 +147,14 @@ func InteractiveConfig(binaryName string) { conf := &config.CothoritydConfig{ Public: pubStr, Private: privStr, - Address: serverBinding, + Address: publicAddress, + Description: config.Input("New cothority", + "Give a description of the cothority"), } var configDone bool var configFolder string - var defaultFolder = path.Dir(getDefaultConfigFile(binaryName)) + var defaultFolder = path.Dir(GetDefaultConfigFile(binaryName)) var configFile string var groupFile string @@ -180,7 +182,7 @@ func InteractiveConfig(binaryName string) { log.Fatal("Impossible to parse public key:", err) } - server := config.NewServerToml(network.Suite, public, publicAddress) + server := config.NewServerToml(network.Suite, public, publicAddress, conf.Description) group := config.NewGroupToml(server) saveFiles(conf, configFile, group, groupFile) @@ -218,21 +220,22 @@ func CheckServers(g *config.Group) error { desc = []string{d, d} } el := sda.NewRoster([]*network.ServerIdentity{e}) - success = success && checkList(el, desc) == nil + success = checkList(el, desc) == nil && success } if len(g.Roster.List) > 1 { // Then check pairs of servers for i, first := range g.Roster.List { for _, second := range g.Roster.List[i+1:] { + log.Lvl3("Testing connection between", first, second) desc := []string{"none", "none"} if d1 := g.GetDescription(first); d1 != "" { desc = []string{d1, g.GetDescription(second)} } es := []*network.ServerIdentity{first, second} - success = success && checkList(sda.NewRoster(es), desc) == nil + success = checkList(sda.NewRoster(es), desc) == nil && success es[0], es[1] = es[1], es[0] desc[0], desc[1] = desc[1], desc[0] - success = success && checkList(sda.NewRoster(es), desc) == nil + success = checkList(sda.NewRoster(es), desc) == nil && success } } } @@ -255,7 +258,7 @@ func checkList(list *sda.Roster, descs []string) error { } log.Lvl3("Sending message to: " + serverStr) msg := "verification" - log.Info("Checking server(s) ", serverStr, ": ") + fmt.Printf("Checking server(s) %s: ", serverStr) sig, err := signStatement(strings.NewReader(msg), list) if err != nil { log.Error(err) @@ -266,7 +269,7 @@ func checkList(list *sda.Roster, descs []string) error { log.Errorf("Invalid signature: %v", err) return err } - log.Info("Success") + fmt.Print("Success\n") return nil } @@ -297,7 +300,7 @@ func signStatement(read io.Reader, el *sda.Roster) (*s.SignatureResponse, case response, ok := <-pchan: log.Lvl5("Response:", response) if !ok || err != nil { - return nil, errors.New("Received an invalid repsonse.") + return nil, errors.New("received an invalid response") } err = cosi.VerifySignature(network.Suite, el.Publics(), msg, response.Signature) if err != nil { @@ -389,10 +392,10 @@ func saveFiles(conf *config.CothoritydConfig, fileConf string, group *config.Gro } -// getDefaultConfigFile returns the default path to the configuration-path, which +// GetDefaultConfigFile returns the default path to the configuration-path, which // is ~/.config/binaryName for Unix and ~/Library/binaryName for MacOSX. // In case of an error it Fatals. -func getDefaultConfigFile(binaryName string) string { +func GetDefaultConfigFile(binaryName string) string { u, err := user.Current() // can't get the user dir, so fallback to current working dir if err != nil { @@ -409,7 +412,7 @@ func getDefaultConfigFile(binaryName string) string { return path.Join(u.HomeDir, "Library", binaryName, DefaultServerConfig) default: return path.Join(u.HomeDir, ".config", binaryName, DefaultServerConfig) - // TODO WIndows ? FreeBSD ? + // TODO Windows? FreeBSD? } } @@ -496,3 +499,17 @@ func tryConnect(ip, binding network.Address) error { } return nil } + +// RunServer starts a cothority server with the given config file name. It can +// be used by different apps (like CoSi, for example) +func RunServer(configFilename string) { + if _, err := os.Stat(configFilename); os.IsNotExist(err) { + log.Fatalf("[-] Configuration file does not exists. %s", configFilename) + } + // Let's read the config + _, conode, err := config.ParseCothorityd(configFilename) + if err != nil { + log.Fatal("Couldn't parse config:", err) + } + conode.Start() +} diff --git a/app/lib/test/cothorityd.sh b/app/lib/test/cothorityd.sh index 46770387..e85b9d18 100644 --- a/app/lib/test/cothorityd.sh +++ b/app/lib/test/cothorityd.sh @@ -3,7 +3,7 @@ DBG_SRV=${DBG_SRV:-0} runCoCfg(){ - echo -e "127.0.0.1:200$1\nco$1\n\n" | dbgRun runCo $1 setup + echo -e "127.0.0.1:200$1\nNew Cothority $1\nco$1\n" | dbgRun runCo $1 setup } runCoBG(){ diff --git a/app/status/status.go b/app/status/status.go index 1e3d9e1f..4e87995a 100644 --- a/app/status/status.go +++ b/app/status/status.go @@ -15,7 +15,7 @@ import ( "strings" "github.com/dedis/cothority/services/status" - "gopkg.in/codegangsta/cli.v1" + "gopkg.in/urfave/cli.v1" ) func main() { diff --git a/app/test_cosi.sh b/app/test_cosi.sh new file mode 100755 index 00000000..c11008fd --- /dev/null +++ b/app/test_cosi.sh @@ -0,0 +1,136 @@ +#!/usr/bin/env bash + +DBG_SHOW=1 +# Debug-level for app +DBG_APP=1 +# Uncomment to build in local dir +# STATICDIR=test + +. lib/test/libtest.sh +. lib/test/cothorityd.sh + +tails=8 + +main(){ + startTest + build + test Build + test ServerCfg + test SignFile + test Check + test Reconnect + stopTest +} + +testReconnect(){ + for s in 1 2; do + setupServers 1 + testOut "Running first sign" + echo "My Test Message File" > foo.txt + testOK runCl 1 sign foo.txt + testOut "Killing server $s" + pkill -f "c srv$s/config" + testFail runCl 1 sign foo.txt + testOut "Starting server $s again" + runSrv $s + sleep 1 + testOK runCl 1 sign foo.txt + pkill -f ./cosi + done +} + +testCheck(){ + setupServers 1 + testOK runCl 1 check + runSrvCfg 3 + tail -n 4 srv3/group.toml >> cl1/servers.toml + testFail runCl 1 check +} + +testSignFile(){ + setupServers 1 + echo "Running first sign" + echo "My Test Message File" > foo.txt + echo "My Second Test Message File" > bar.txt + runCl 1 sign foo.txt > /dev/null + echo "Running second sign" + runCl 1 sign foo.txt -o cl1/signature > /dev/null + testOK runCl 1 verify foo.txt -s cl1/signature + testFail runCl 1 verify bar.txt -s cl1/signature + rm foo.txt + rm bar.txt +} + +testServerCfg(){ + runSrvCfg 1 + pkill cosi + testFile srv1/config.toml +} + +testBuild(){ + testOK ./cosi help +} + +setupServers(){ + CLIENT=$1 + OOUT=$OUT + OUT=/tmp/config + SERVERS=cl$CLIENT/servers.toml + rm -f srv1/* + rm -f srv2/* + runSrvCfg 1 + tail -n 4 srv1/group.toml > $SERVERS + runSrvCfg 2 + echo >> $SERVERS + tail -n 4 srv2/group.toml >> $SERVERS + runSrv 1 + runSrv 2 + OUT=$OOUT +} + +runCl(){ + local D=cl$1/servers.toml + shift + echo "Running Client with $D $@" + dbgRun ./cosi -d $DBG_APP $@ -g $D +} + +runSrvCfg(){ + echo -e "127.0.0.1:200$1\nCosi $1\n$(pwd)/srv$1\n" | ./cosi server setup > $OUT +} + +runSrv(){ + ( ./cosi -d $DBG_SRV server -c srv$1/config.toml & ) +} + +build(){ + BUILDDIR=$(pwd) + if [ "$STATICDIR" ]; then + DIR=$STATICDIR + else + DIR=$(mktemp -d) + fi + mkdir -p $DIR + cd $DIR + echo "Building in $DIR" + if [ ! -x cosi ]; then + go build -o cosi $BUILDDIR/cosi/*go + fi + for n in $(seq $NBR); do + srv=srv$n + rm -rf $srv + mkdir $srv + cl=cl$n + rm -rf $cl + mkdir $cl + done +} + +if [ "$1" == "-q" ]; then + DBG_RUN= + STATICDIR= +elif [ "$1" ]; then + rm -f $STATICDIR/cosi +fi + +main diff --git a/app/test_cothorityd.sh b/app/test_cothorityd.sh index dcac15d4..3d16db7c 100755 --- a/app/test_cothorityd.sh +++ b/app/test_cothorityd.sh @@ -3,8 +3,6 @@ DBG_SHOW=1 # Debug-level for server DBG_SRV=0 -# Debug-level for client -DBG_CLIENT=0 # For easier debugging #STATICDIR=test @@ -28,18 +26,13 @@ testCothorityd(){ sleep 1 cp co1/group.toml . tail -n 4 co2/group.toml >> group.toml - testOK runCosi check -g group.toml + testOK runCo 1 check -g group.toml tail -n 4 co3/group.toml >> group.toml - testFail runCosi check -g group.toml + testFail runCo 1 check -g group.toml } testBuild(){ testOK dbgRun ./cothorityd --help - testOK dbgRun ./cosi --help -} - -runCosi(){ - dbgRun ./cosi -d $DBG_CLIENT $@ } build(){ @@ -53,7 +46,7 @@ build(){ cd $DIR echo "Building in $DIR" - for appdir in $BUILDDIR/cothorityd $GOPATH/src/github.com/dedis/cosi; do + for appdir in $BUILDDIR/cothorityd; do app=$(basename $appdir) if [ ! -e $app -o "$BUILD" ]; then if ! go build -o $app $appdir/*.go; then @@ -66,15 +59,11 @@ build(){ co=co$n rm -f $co/* mkdir -p $co - - cosi=cosi$n - rm -f $cosi/* - mkdir -p $cosi done } if [ "$1" -a "$STATICDIR" ]; then - rm -f $STATICDIR/{cothorityd,cosi} + rm -f $STATICDIR/cothorityd fi main diff --git a/coveralls.sh b/coveralls.sh index bd2239c2..523853c9 100755 --- a/coveralls.sh +++ b/coveralls.sh @@ -6,16 +6,6 @@ DIR_SOURCE="$(find . -maxdepth 10 -type f -not -path '*/vendor*' -name '*.go' | BRANCH=$TRAVIS_PULL_REQUEST_BRANCH echo "Using branch $BRANCH" -pattern="refactor_*" -if [[ $BRANCH =~ $pattern ]]; then - echo "Using refactored branch $BRANCH - fetching cosi" - repo=github.com/dedis/cosi - go get -d $repo - cd $GOPATH/src/$repo - git checkout -f $BRANCH - echo $(git rev-parse --abbrev-ref HEAD) -fi - if [ "$TRAVIS_BUILD_DIR" ]; then cd $TRAVIS_BUILD_DIR fi @@ -27,6 +17,7 @@ echo "mode: atomic" > profile.cov for dir in ${DIR_SOURCE}; do go test -short -race -covermode=atomic -coverprofile=$dir/profile.tmp $dir + if [ $? -ne 0 ]; then all_tests_passed=false fi diff --git a/install.sh b/install.sh deleted file mode 100755 index 2cbf9fc2..00000000 --- a/install.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/bin/bash - -## install.sh will fetch all dependencies of the project. -## If the branch from where the PR comes from starts with -## `refactor_`, it will checkout the same branch name -## in dedis/cosi. - -# Temporarily overwrite the branch -BRANCH=protocols_conode_632 - -if [ "$TRAVIS_PULL_REQUEST" = "false" ]; then - BRANCH=$TRAVIS_BRANCH -elif [ "$BRANCH" ]; then - echo "Manual override of branch to: $BRANCH" -else - # http://graysonkoonce.com/getting-the-current-branch-name-during-a-pull-request-in-travis-ci/ - PR=https://api.github.com/repos/$TRAVIS_REPO_SLUG/pulls/$TRAVIS_PULL_REQUEST - BRANCH=$(curl -s $PR | jq -r .head.ref ) - if [ "$BRANCH" = "null" ]; then - echo "Couldn't fetch branch - probably too many requests." - echo "Please set your own branch manually in install.sh" - exit 1 - fi -fi - -echo "Using branch $BRANCH" - -#pattern="refactor_*" -pattern="protocols_conode_632" -if [[ $BRANCH =~ $pattern ]]; then - echo "Using refactored branch $BRANCH - fetching cosi" - repo=github.com/dedis/cosi - go get -d $repo - cd $GOPATH/src/$repo - git checkout -f $BRANCH - echo $(git rev-parse --abbrev-ref HEAD) -fi - -cd $TRAVIS_BUILD_DIR -go get -t ./... diff --git a/log/log.go b/log/log.go new file mode 100644 index 00000000..683e6aa8 --- /dev/null +++ b/log/log.go @@ -0,0 +1,110 @@ +// Package log is an output-library that can print nicely formatted +// messages to the screen. +// +// There are log-level messages that will be printed according to the +// current debug-level set. Furthermore a set of common messages exist +// that are printed according to a chosen format. +// +// The log-level messages are: +// log.Lvl1("Important information") +// log.Lvl2("Less important information") +// log.Lvl3("Eventually flooding information") +// log.Lvl4("Definitively flooding information") +// log.Lvl5("I hope you never need this") +// in your program, then according to the debug-level one or more levels of +// output will be shown. To set the debug-level, use +// log.SetDebugVisible(3) +// which will show all `Lvl1`, `Lvl2`, and `Lvl3`. If you want to turn +// on just one output, you can use +// log.LLvl2("Less important information") +// By adding a single 'L' to the method, it *always* gets printed. +// +// You can also add a 'f' to the name and use it like fmt.Printf: +// log.Lvlf1("Level: %d/%d", now, max) +// +// The common messages are: +// log.Print("Simple output") +// log.Info("For your information") +// log.Warn("Only a warning") +// log.Error("This is an error, but continues") +// log.Panic("Something really went bad - calls panic") +// log.Fatal("No way to continue - calls os.Exit") +// +// These messages are printed according to the value of 'Format': +// - Format == FormatLvl - same as log.Lvl +// - Format == FormatPython - with some nice python-style formatting +// - Format == FormatNone - just as plain text +// +// The log-package also takes into account the following environment-variables: +// DEBUG_LVL // will act like SetDebugVisible +// DEBUG_TIME // if 'true' it will print the date and time +// DEBUG_COLOR // if 'false' it will not use colors +// But for this the function ParseEnv() or AddFlags() has to be called. +package log + +import ( + "bytes" + "io" + "os" + "strings" +) + +// For testing we can change the output-writer +var stdOut io.Writer +var stdErr io.Writer + +func init() { + stdOut = os.Stdout + stdErr = os.Stderr +} + +var bufStdOut bytes.Buffer +var bufStdErr bytes.Buffer + +// OutputToBuf is called for sending all the log.*-outputs to internal buffers +// that can be used for checking what the logger would've written. This is +// mostly used for tests. +func OutputToBuf() { + debugMut.Lock() + defer debugMut.Unlock() + stdOut = &bufStdOut + stdErr = &bufStdErr +} + +// OutputToOs redirects the output of the log.*-outputs again to the os. +func OutputToOs() { + debugMut.Lock() + defer debugMut.Unlock() + stdOut = os.Stdout + stdErr = os.Stderr +} + +// GetStdOut returns all log.*-outputs to StdOut since the last call. +func GetStdOut() string { + debugMut.Lock() + defer debugMut.Unlock() + ret := bufStdOut.String() + bufStdOut.Reset() + return ret +} + +// GetStdErr returns all log.*-outputs to StdErr since the last call. +func GetStdErr() string { + debugMut.Lock() + defer debugMut.Unlock() + ret := bufStdErr.String() + bufStdErr.Reset() + return ret +} + +// ContainsStdErr will look for str in StdErr and flush the output-buffer. +// If you need to look at multiple strings, use GetStdErr. +func ContainsStdErr(str string) bool { + return strings.Contains(GetStdErr(), str) +} + +// ContainsStdOut will look for str in StdOut and flush the output-buffer. +// If you need to look at multiple strings, use GetStdOut. +func ContainsStdOut(str string) bool { + return strings.Contains(GetStdOut(), str) +} diff --git a/log/log_test.go b/log/log_test.go new file mode 100644 index 00000000..4c1206e3 --- /dev/null +++ b/log/log_test.go @@ -0,0 +1,24 @@ +package log + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestContains(t *testing.T) { + // Flush buffers from previous tests. + GetStdOut() + GetStdErr() + old := DebugVisible() + SetDebugVisible(1) + + assert.False(t, ContainsStdOut("info")) + assert.False(t, ContainsStdErr("error")) + Info("Some information") + assert.True(t, ContainsStdOut("info")) + Error("Some error") + assert.True(t, ContainsStdErr("error")) + + SetDebugVisible(old) +} diff --git a/log/lvl.go b/log/lvl.go index 7de0b25f..5e0c4637 100644 --- a/log/lvl.go +++ b/log/lvl.go @@ -1,51 +1,8 @@ -// Package log is an output-library that can print nicely formatted -// messages to the screen. -// -// There are log-level messages that will be printed according to the -// current debug-level set. Furthermore a set of common messages exist -// that are printed according to a chosen format. -// -// The log-level messages are: -// log.Lvl1("Important information") -// log.Lvl2("Less important information") -// log.Lvl3("Eventually flooding information") -// log.Lvl4("Definitively flooding information") -// log.Lvl5("I hope you never need this") -// in your program, then according to the debug-level one or more levels of -// output will be shown. To set the debug-level, use -// log.SetDebugVisible(3) -// which will show all `Lvl1`, `Lvl2`, and `Lvl3`. If you want to turn -// on just one output, you can use -// log.LLvl2("Less important information") -// By adding a single 'L' to the method, it *always* gets printed. -// -// You can also add a 'f' to the name and use it like fmt.Printf: -// log.Lvlf1("Level: %d/%d", now, max) -// -// The common messages are: -// log.Print("Simple output") -// log.Info("For your information") -// log.Warn("Only a warning") -// log.Error("This is an error, but continues") -// log.Panic("Something really went bad - calls panic") -// log.Fatal("No way to continue - calls os.Exit") -// -// These messages are printed according to the value of 'Format': -// - Format == FormatLvl - same as log.Lvl -// - Format == FormatPython - with some nice python-style formatting -// - Format == FormatNone - just as plain text -// -// The log-package also takes into account the following environment-variables: -// DEBUG_LVL // will act like SetDebugVisible -// DEBUG_TIME // if 'true' it will print the date and time -// DEBUG_COLOR // if 'false' it will not use colors -// But for this the function ParseEnv() or AddFlags() has to be called. package log import ( "flag" "fmt" - "io" "os" "regexp" "runtime" @@ -59,16 +16,6 @@ import ( "github.com/daviddengcn/go-colortext" ) -// For debugging purposes we can change the output-writer -var stdOut io.Writer -var stdErr io.Writer - -func init() { - ParseEnv() - stdOut = os.Stdout - stdErr = os.Stderr -} - const ( lvlWarning = iota - 20 lvlError @@ -128,6 +75,10 @@ var debugMut sync.RWMutex var regexpPaths, _ = regexp.Compile(".*/") +func init() { + ParseEnv() +} + func lvl(lvl, skip int, args ...interface{}) { debugMut.Lock() defer debugMut.Unlock() diff --git a/log/lvl_test.go b/log/lvl_test.go index 6e378b4c..dd8f6df7 100644 --- a/log/lvl_test.go +++ b/log/lvl_test.go @@ -6,6 +6,8 @@ import ( "testing" "errors" + + "github.com/stretchr/testify/assert" ) func init() { @@ -15,16 +17,13 @@ func init() { func TestTime(t *testing.T) { SetDebugVisible(1) - getStdOut() + GetStdOut() Lvl1("No time") - str := getStdOut() - if !strings.Contains(str, "1 : (") { - t.Fatal("Didn't get correct string: ", str) - } + assert.True(t, ContainsStdOut("1 : (")) SetShowTime(true) defer func() { SetShowTime(false) }() Lvl1("With time") - str = getStdOut() + str := GetStdOut() if strings.Contains(str, "1 : (") { t.Fatal("Didn't get correct string: ", str) } @@ -98,8 +97,8 @@ func TestOutputFuncs(t *testing.T) { func checkOutput(f func(), wantsStd, wantsErr bool) error { f() - stdStr := getStdOut() - errStr := getStdErr() + stdStr := GetStdOut() + errStr := GetStdErr() if wantsStd { if len(stdStr) == 0 { return errors.New("Stdout was empty") @@ -123,13 +122,13 @@ func checkOutput(f func(), wantsStd, wantsErr bool) error { func ExampleLvl2() { SetDebugVisible(2) - stdToOs() + OutputToOs() Lvl1("Level1") Lvl2("Level2") Lvl3("Level3") Lvl4("Level4") Lvl5("Level5") - stdToBuf() + OutputToBuf() SetDebugVisible(1) // Output: @@ -138,21 +137,21 @@ func ExampleLvl2() { } func ExampleLvl1() { - stdToOs() + OutputToOs() Lvl1("Multiple", "parameters") - stdToBuf() + OutputToBuf() // Output: // 1 : ( log.ExampleLvl1: 0) - Multiple parameters } func ExampleLLvl1() { - stdToOs() + OutputToOs() Lvl1("Lvl output") LLvl1("LLvl output") Lvlf1("Lvlf output") LLvlf1("LLvlf output") - stdToBuf() + OutputToBuf() // Output: // 1 : ( log.ExampleLLvl1: 0) - Lvl output @@ -162,16 +161,16 @@ func ExampleLLvl1() { } func thisIsAVeryLongFunctionNameThatWillOverflow() { - stdToOs() + OutputToOs() Lvl1("Overflow") } func ExampleLvlf1() { - stdToOs() + OutputToOs() Lvl1("Before") thisIsAVeryLongFunctionNameThatWillOverflow() Lvl1("After") - stdToBuf() + OutputToBuf() // Output: // 1 : ( log.ExampleLvlf1: 0) - Before @@ -181,11 +180,11 @@ func ExampleLvlf1() { func ExampleLvl3() { NamePadding = -1 - stdToOs() + OutputToOs() Lvl1("Before") thisIsAVeryLongFunctionNameThatWillOverflow() Lvl1("After") - stdToBuf() + OutputToBuf() // Output: // 1 : (log.ExampleLvl3: 0) - Before diff --git a/log/ui.go b/log/ui.go index e4faea53..50a9600a 100644 --- a/log/ui.go +++ b/log/ui.go @@ -7,7 +7,7 @@ import ( ) func lvlUI(l int, args ...interface{}) { - if debugVisible > 0 { + if DebugVisible() > 0 { lvl(l, 3, args...) } else { print(l, args...) @@ -95,6 +95,8 @@ func ErrFatalf(err error, f string, args ...interface{}) { } func print(lvl int, args ...interface{}) { + debugMut.Lock() + defer debugMut.Unlock() switch debugVisible { case FormatPython: prefix := []string{"[-]", "[!]", "[X]", "[Q]", "[+]", ""} diff --git a/log/ui_test.go b/log/ui_test.go index 59adb307..03aafdbf 100644 --- a/log/ui_test.go +++ b/log/ui_test.go @@ -3,70 +3,32 @@ package log import ( "testing" - "bufio" - "bytes" - "os" - "github.com/stretchr/testify/assert" ) -var bufStdOut bytes.Buffer -var testStdOut *bufio.Writer -var bufStdErr bytes.Buffer -var testStdErr *bufio.Writer - -func stdToBuf() { - testStdOut = bufio.NewWriter(&bufStdOut) - stdOut = testStdOut - testStdErr = bufio.NewWriter(&bufStdErr) - stdErr = testStdErr -} - -func stdToOs() { - stdOut = os.Stdout - stdErr = os.Stderr -} - -func getStdOut() string { - testStdOut.Flush() - ret := bufStdOut.String() - bufStdOut.Reset() - return ret -} - -func getStdErr() string { - testStdErr.Flush() - ret := bufStdErr.String() - bufStdErr.Reset() - return ret -} - func TestMain(m *testing.M) { - stdToBuf() + OutputToBuf() MainTest(m) } func TestInfo(t *testing.T) { SetDebugVisible(FormatPython) Info("Python") - assert.Equal(t, "[+] Python\n", getStdOut()) + assert.True(t, ContainsStdOut("[+] Python\n")) SetDebugVisible(FormatNone) Info("None") - assert.Equal(t, "None\n", getStdOut()) + assert.True(t, ContainsStdOut("None\n")) Info("None", "Python") - assert.Equal(t, "None Python\n", getStdOut()) + assert.True(t, ContainsStdOut("None Python\n")) SetDebugVisible(1) } func TestLvl(t *testing.T) { SetDebugVisible(1) Info("TestLvl") - assert.Equal(t, "I : ( log.TestLvl: 0) - TestLvl\n", - getStdOut()) + assert.True(t, ContainsStdOut("I : ( log.TestLvl: 0) - TestLvl\n")) Print("TestLvl") - assert.Equal(t, "I : ( log.TestLvl: 0) - TestLvl\n", - getStdOut()) + assert.True(t, ContainsStdOut("I : ( log.TestLvl: 0) - TestLvl\n")) Warn("TestLvl") - assert.Equal(t, "W : ( log.TestLvl: 0) - TestLvl\n", - getStdErr()) + assert.True(t, ContainsStdErr("W : ( log.TestLvl: 0) - TestLvl\n")) } diff --git a/monitor/measure.go b/monitor/measure.go index 0198567a..2a6a1768 100644 --- a/monitor/measure.go +++ b/monitor/measure.go @@ -42,8 +42,8 @@ type Measure interface { Record() } -// SingleMeasure is a pair name - value we want to send -type SingleMeasure struct { +// SingleMeasure is a pair name - value we want to send to the monitor. +type singleMeasure struct { Name string Value float64 } @@ -51,9 +51,9 @@ type SingleMeasure struct { // TimeMeasure represents a measure regarding time: It includes the wallclock // time, the cpu time + the user time. type TimeMeasure struct { - Wall *SingleMeasure - CPU *SingleMeasure - User *SingleMeasure + Wall *singleMeasure + CPU *singleMeasure + User *singleMeasure // non exported fields // name of the time measure (basename) name string @@ -80,20 +80,23 @@ func ConnectSink(addr string) error { return nil } -// NewSingleMeasure returns a new measure freshly generated -func NewSingleMeasure(name string, value float64) *SingleMeasure { - return &SingleMeasure{ +// RecordSingleMeasure sends the pair name - value to the monitor directly. +func RecordSingleMeasure(name string, value float64) { + sm := newSingleMeasure(name, value) + sm.Record() +} + +func newSingleMeasure(name string, value float64) *singleMeasure { + return &singleMeasure{ Name: name, Value: value, } } -// Record sends the value to the monitor. Reset the value to 0. -func (sm *SingleMeasure) Record() { - if err := send(sm); err != nil { - log.Error("Error sending SingleMeasure", sm.Name, " to monitor:", err) +func (s *singleMeasure) Record() { + if err := send(s); err != nil { + log.Error("Error sending SingleMeasure", s.Name, " to monitor:", err) } - sm.Value = 0 } // NewTimeMeasure return *TimeMeasure @@ -112,7 +115,7 @@ func NewTimeMeasure(name string) *TimeMeasure { // - user time: *name*_user func (tm *TimeMeasure) Record() { // Wall time measurement - tm.Wall = NewSingleMeasure(tm.name+"_wall", float64(time.Since(tm.lastWallTime))/1.0e9) + tm.Wall = newSingleMeasure(tm.name+"_wall", float64(time.Since(tm.lastWallTime))/1.0e9) // CPU time measurement tm.CPU.Value, tm.User.Value = getDiffRTime(tm.CPU.Value, tm.User.Value) // send data @@ -127,8 +130,8 @@ func (tm *TimeMeasure) Record() { // reset reset the time fields of this time measure func (tm *TimeMeasure) reset() { cpuTimeSys, cpuTimeUser := getRTime() - tm.CPU = NewSingleMeasure(tm.name+"_system", cpuTimeSys) - tm.User = NewSingleMeasure(tm.name+"_user", cpuTimeUser) + tm.CPU = newSingleMeasure(tm.name+"_system", cpuTimeSys) + tm.User = newSingleMeasure(tm.name+"_user", cpuTimeUser) tm.lastWallTime = time.Now() } @@ -171,10 +174,10 @@ func (cm *CounterIOMeasure) Record() { bRx := cm.counter.Rx() // TODO Later on, we might want to do a check on the conversion between // uint64 -> float64, as the MAX values are not the same. - read := NewSingleMeasure(cm.name+"_rx", float64(bRx-cm.baseRx)) + read := newSingleMeasure(cm.name+"_rx", float64(bRx-cm.baseRx)) // creates the written measure bTx := cm.counter.Tx() - written := NewSingleMeasure(cm.name+"_tx", float64(bTx-cm.baseTx)) + written := newSingleMeasure(cm.name+"_tx", float64(bTx-cm.baseTx)) // send them both read.Record() @@ -215,7 +218,7 @@ func send(v interface{}) error { // EndAndCleanup sends a message to end the logging and closes the connection func EndAndCleanup() { - if err := send(NewSingleMeasure("end", 0)); err != nil { + if err := send(newSingleMeasure("end", 0)); err != nil { log.Error("Error while sending 'end' message:", err) } if err := connection.Close(); err != nil { diff --git a/monitor/monitor.go b/monitor/monitor.go index 5822e4d3..0622af76 100644 --- a/monitor/monitor.go +++ b/monitor/monitor.go @@ -53,7 +53,7 @@ type Monitor struct { mutexStats sync.Mutex // channel to give new measures - measures chan *SingleMeasure + measures chan *singleMeasure // channel to notify the end of a connection // send the name of the connection when finishd @@ -69,7 +69,7 @@ func NewMonitor(stats *Stats) *Monitor { stats: stats, mutexStats: sync.Mutex{}, SinkPort: DefaultSinkPort, - measures: make(chan *SingleMeasure), + measures: make(chan *singleMeasure), done: make(chan string), listenerLock: new(sync.Mutex), } @@ -166,7 +166,7 @@ func (m *Monitor) handleConnection(conn net.Conn) { dec := json.NewDecoder(conn) nerr := 0 for { - measure := &SingleMeasure{} + measure := &singleMeasure{} if err := dec.Decode(measure); err != nil { // if end of connection if err == io.EOF || strings.Contains(err.Error(), "closed") { @@ -195,7 +195,7 @@ func (m *Monitor) handleConnection(conn net.Conn) { // updateMeasures will add that specific measure to the global stats // in a concurrently safe manner -func (m *Monitor) update(meas *SingleMeasure) { +func (m *Monitor) update(meas *singleMeasure) { m.mutexStats.Lock() // updating m.stats.Update(meas) diff --git a/monitor/monitor_test.go b/monitor/monitor_test.go index e6aa3cea..7878700a 100644 --- a/monitor/monitor_test.go +++ b/monitor/monitor_test.go @@ -31,10 +31,10 @@ func TestReadyNormal(t *testing.T) { return } - meas := NewSingleMeasure("round", 10) + meas := newSingleMeasure("round", 10) meas.Record() time.Sleep(200 * time.Millisecond) - NewSingleMeasure("round", 20) + newSingleMeasure("round", 20) EndAndCleanup() time.Sleep(100 * time.Millisecond) updated := mon.Stats().String() @@ -50,8 +50,8 @@ func TestKeyOrder(t *testing.T) { m["bf"] = "2" // create stats stat := NewStats(m) - m1 := NewSingleMeasure("round", 10) - m2 := NewSingleMeasure("setup", 5) + m1 := newSingleMeasure("round", 10) + m2 := newSingleMeasure("setup", 5) stat.Update(m1) stat.Update(m2) str := new(bytes.Buffer) diff --git a/monitor/proxy.go b/monitor/proxy.go index 1a802b49..cdbc830c 100644 --- a/monitor/proxy.go +++ b/monitor/proxy.go @@ -97,7 +97,7 @@ func Proxy(redirection string) error { nconn-- if nconn == 0 { // everything is finished - if err := serverEnc.Encode(NewSingleMeasure("end", 0)); err != nil { + if err := serverEnc.Encode(newSingleMeasure("end", 0)); err != nil { log.Error("Couldn't send 'end' message:", err) } if err := serverConn.Close(); err != nil { @@ -133,7 +133,7 @@ func proxyConnection(conn net.Conn, done chan bool) { dec := json.NewDecoder(conn) nerr := 0 for { - m := SingleMeasure{} + m := singleMeasure{} // Receive data if err := dec.Decode(&m); err != nil { if err == io.EOF { diff --git a/monitor/stats.go b/monitor/stats.go index 0d9ff287..83c7f3dc 100644 --- a/monitor/stats.go +++ b/monitor/stats.go @@ -54,7 +54,7 @@ func (s *Stats) init() *Stats { } // Update will update the Stats with this given measure -func (s *Stats) Update(m *SingleMeasure) { +func (s *Stats) Update(m *singleMeasure) { var value *Value var ok bool s.valuesMutex.Lock() diff --git a/monitor/stats_test.go b/monitor/stats_test.go index 0e595b3f..74c4ad99 100644 --- a/monitor/stats_test.go +++ b/monitor/stats_test.go @@ -47,8 +47,8 @@ func TestStatsUpdate(t *testing.T) { rc["hosts"] = "2" stats := NewStats(rc) - m1 := NewSingleMeasure("round_wall", 10) - m2 := NewSingleMeasure("round_wall", 30) + m1 := newSingleMeasure("round_wall", 10) + m2 := newSingleMeasure("round_wall", 30) stats.Update(m1) stats.Update(m2) stats.Collect() @@ -64,8 +64,8 @@ func TestStatsOrder(t *testing.T) { m["bf"] = "2" // create stats stat := NewStats(m) - m1 := NewSingleMeasure("round", 10) - m2 := NewSingleMeasure("setup", 5) + m1 := newSingleMeasure("round", 10) + m2 := newSingleMeasure("setup", 5) stat.Update(m1) stat.Update(m2) str := new(bytes.Buffer) @@ -104,8 +104,8 @@ func TestStatsAverage(t *testing.T) { // create stats stat1 := NewStats(m) stat2 := NewStats(m) - m1 := NewSingleMeasure("round", 10) - m2 := NewSingleMeasure("setup", 5) + m1 := newSingleMeasure("round", 10) + m2 := newSingleMeasure("setup", 5) stat1.Update(m1) stat2.Update(m2) @@ -139,9 +139,9 @@ func TestStatsAverageFiltered(t *testing.T) { // create stats stat1 := NewStats(m) stat2 := NewStats(m) - m1 := NewSingleMeasure("round", 10) - m2 := NewSingleMeasure("round", 20) - m3 := NewSingleMeasure("round", 150) + m1 := newSingleMeasure("round", 10) + m2 := newSingleMeasure("round", 20) + m3 := newSingleMeasure("round", 150) stat1.Update(m1) stat1.Update(m2) stat1.Update(m3) diff --git a/network/address.go b/network/address.go index f07a18f7..ae41d023 100644 --- a/network/address.go +++ b/network/address.go @@ -122,6 +122,10 @@ func (a Address) Host() string { if e != nil { return "" } + // IPv6 unspecified address has to be in brackets. + if h == "::" { + h = "[::]" + } return h } @@ -148,7 +152,8 @@ func (a Address) Port() string { func (a Address) Public() bool { private, err := regexp.MatchString("(^127\\.)|(^10\\.)|"+ "(^172\\.1[6-9]\\.)|(^172\\.2[0-9]\\.)|"+ - "(^172\\.3[0-1]\\.)|(^192\\.168\\.)|(^169\\.254)", a.NetworkAddress()) + "(^172\\.3[0-1]\\.)|(^192\\.168\\.)|(^169\\.254)|"+ + "(^\\[::\\])", a.NetworkAddress()) if err != nil { return false } diff --git a/network/address_test.go b/network/address_test.go index e9c4784d..22cffe30 100644 --- a/network/address_test.go +++ b/network/address_test.go @@ -3,6 +3,7 @@ package network import ( "testing" + "github.com/dedis/cothority/log" "github.com/stretchr/testify/assert" ) @@ -39,6 +40,7 @@ func TestAddress(t *testing.T) { {"tcp://10.0.0.4:2000", true, PlainTCP, "10.0.0.4:2000", "10.0.0.4", "2000", false}, {"tcp://67.43.129.85:2000", true, PlainTCP, "67.43.129.85:2000", "67.43.129.85", "2000", true}, {"purb://10.0.0.4:2000", true, PURB, "10.0.0.4:2000", "10.0.0.4", "2000", false}, + {"tls://[::]:1000", true, TLS, "[::]:1000", "[::]", "1000", false}, {"tls4://10.0.0.4:2000", false, InvalidConnType, "", "", "", false}, {"tls://1000.0.0.4:2000", false, InvalidConnType, "", "", "", false}, {"tls://10.0.0.4:20000000", false, InvalidConnType, "", "", "", false}, @@ -51,6 +53,7 @@ func TestAddress(t *testing.T) { } for i, str := range tests { + log.Lvl1("Testing", str) add := Address(str.Value) assert.Equal(t, str.Valid, add.Valid(), "Address (%d) %s", i, str.Value) assert.Equal(t, str.Type, add.ConnType(), "Address (%d) %s", i, str.Value) diff --git a/network/client_test.go b/network/client_test.go index 280aa86d..c244a4a6 100644 --- a/network/client_test.go +++ b/network/client_test.go @@ -24,6 +24,9 @@ func testClient(t *testing.T, fac routerFactory, cl clientFactory) { t.Fatal(err) } go r.Start() + for !r.Listening() { + time.Sleep(20 * time.Millisecond) + } proc := NewSendBackProc(t, r) r.RegisterProcessor(proc, SimpleMessageType) diff --git a/network/dispatch.go b/network/dispatch.go index 53bc0487..9c6eab3a 100644 --- a/network/dispatch.go +++ b/network/dispatch.go @@ -111,7 +111,7 @@ func (d *RoutineDispatcher) Dispatch(packet *Packet) error { defer d.Unlock() var p = d.procs[packet.MsgType] if p == nil { - return errors.New("No Processor attached to this message type.") + return errors.New("no Processor attached to this message type") } go p.Process(packet) return nil diff --git a/network/encoding.go b/network/encoding.go index ff052c4d..fb492d29 100644 --- a/network/encoding.go +++ b/network/encoding.go @@ -36,12 +36,17 @@ var ErrorType = PacketTypeID(uuid.Nil) // String returns the name of the structure if it is known, else it returns // the hexadecimal value of the Id. -func (mId PacketTypeID) String() string { - t, ok := registry.get(mId) +func (pId PacketTypeID) String() string { + t, ok := registry.get(pId) if ok { return t.String() } - return fmt.Sprintf("%x", uuid.UUID(mId)) + return uuid.UUID(pId).String() +} + +// Equal returns true if pId is equal to t +func (pId PacketTypeID) Equal(t PacketTypeID) bool { + return bytes.Compare(uuid.UUID(pId).Bytes(), uuid.UUID(t).Bytes()) == 0 } // NamespaceURL is the basic namespace used for uuid @@ -72,7 +77,6 @@ func RegisterPacketUUID(mt PacketTypeID, rt reflect.Type) PacketTypeID { return mt } registry.put(mt, rt) - return mt } @@ -181,13 +185,13 @@ var marshalLock sync.Mutex // MarshalRegisteredType will marshal a struct with its respective type into a // slice of bytes. That slice of bytes can be then decoded in -// UnmarshalRegisteredType. +// UnmarshalRegisteredType. data must be a pointer to the message. func MarshalRegisteredType(data Body) ([]byte, error) { marshalLock.Lock() defer marshalLock.Unlock() var msgType PacketTypeID if msgType = TypeFromData(data); msgType == ErrorType { - return nil, fmt.Errorf("Type of message %s not registered to the network library.", reflect.TypeOf(data)) + return nil, fmt.Errorf("type of message %s not registered to the network library", reflect.TypeOf(data)) } b := new(bytes.Buffer) if err := binary.Write(b, globalOrder, msgType); err != nil { @@ -217,7 +221,7 @@ func UnmarshalRegisteredType(buf []byte, constructors protobuf.Constructors) (Pa } typ, ok := registry.get(tID) if !ok { - return ErrorType, nil, fmt.Errorf("Type %s not registered.", + return ErrorType, nil, fmt.Errorf("type %s not registered", typ.Name()) } ptrVal := reflect.New(typ) @@ -238,7 +242,7 @@ func UnmarshalRegistered(buf []byte) (PacketTypeID, Body, error) { } typ, ok := registry.get(tID) if !ok { - return ErrorType, nil, fmt.Errorf("Type %s not registered.", + return ErrorType, nil, fmt.Errorf("type %s not registered", tID) } ptrVal := reflect.New(typ) @@ -270,7 +274,7 @@ func (am *Packet) UnmarshalBinary(buf []byte) error { func NewNetworkPacket(obj Body) (*Packet, error) { val := reflect.ValueOf(obj) if val.Kind() != reflect.Ptr { - return nil, fmt.Errorf("Send takes a pointer to the message, not a copy...") + return nil, fmt.Errorf("Expected a pointer to the message") } ty := TypeFromData(obj) if ty == ErrorType { diff --git a/network/local.go b/network/local.go index 59283786..f72616fc 100644 --- a/network/local.go +++ b/network/local.go @@ -30,9 +30,8 @@ func NewLocalRouterWithManager(lm *LocalManager, sid *ServerIdentity) (*Router, // It also keeps track of who is "listening", so it's possible to mimic // Conn & Listener. type LocalManager struct { - // queues maps a remote endpoint to its packet queue. It's the main - // structure used to communicate. - queues map[endpoint]*connQueue + // conns maps a remote endpoint to the remote connection. + conns map[endpoint]*LocalConn sync.Mutex // The listening-functions used when a new connection-request arrives. listening map[Address]func(Conn) @@ -45,7 +44,7 @@ type LocalManager struct { // LocalListener & LocalHost. func NewLocalManager() *LocalManager { return &LocalManager{ - queues: make(map[endpoint]*connQueue), + conns: make(map[endpoint]*LocalConn), listening: make(map[Address]func(Conn)), } } @@ -115,10 +114,9 @@ func (lm *LocalManager) connect(local, remote Address) (*LocalConn, error) { outgoing := newLocalConn(lm, outEndpoint, incEndpoint) incoming := newLocalConn(lm, incEndpoint, outEndpoint) - // outgoing knows how to store packets into the incoming's queue. - lm.queues[outEndpoint] = outgoing.connQueue - // incoming knows how to store packets into the outgoing's queue. - lm.queues[incEndpoint] = incoming.connQueue + // map the endpoint to the connection + lm.conns[outEndpoint] = outgoing + lm.conns[incEndpoint] = incoming go fn(incoming) return outgoing, nil @@ -130,36 +128,43 @@ func (lm *LocalManager) connect(local, remote Address) (*LocalConn, error) { func (lm *LocalManager) send(e endpoint, msg []byte) error { lm.Lock() defer lm.Unlock() - q, ok := lm.queues[e] + q, ok := lm.conns[e] if !ok { return ErrClosed } - q.push(msg) + q.incomingQueue <- msg return nil } // close gets the connection denoted by this endpoint and closes it if // it is present. -func (lm *LocalManager) close(conn *LocalConn) { +func (lm *LocalManager) close(conn *LocalConn) error { lm.Lock() defer lm.Unlock() + _, ok := lm.conns[conn.local] + if !ok { + // connection already closed + return ErrClosed + } // delete this conn - delete(lm.queues, conn.local) + delete(lm.conns, conn.local) + conn.closeChannels() // and delete the remote one + close it - remote, ok := lm.queues[conn.remote] + remote, ok := lm.conns[conn.remote] if !ok { - return + return nil } - delete(lm.queues, conn.remote) - remote.close() + delete(lm.conns, conn.remote) + remote.closeChannels() + return nil } // len returns how many local connections are open. func (lm *LocalManager) len() int { lm.Lock() defer lm.Unlock() - return len(lm.queues) + return len(lm.conns) } // LocalConn is a connection that sends and receives messages to other @@ -168,10 +173,14 @@ type LocalConn struct { local endpoint remote endpoint - // connQueue is accessible from the LocalManager (i.e. is - // shared). We can't directly share LocalConn because go test - // -race detects it as data race (while it's *protected*). - *connQueue + // the channel where incoming messages are dispatched + incomingQueue chan []byte + // the channel where messages stored can be retrieved with Receive() + outgoingQueue chan []byte + // the channel used to communicate the stopping of the operations + closeCh chan bool + // the confirmation channel for the go routine + closeConfirm chan bool // counter to keep track of how many bytes read / written this connection // has seen. @@ -184,12 +193,17 @@ type LocalConn struct { // connect. It should not be used from the outside, most user want // to use NewLocalConn. func newLocalConn(lm *LocalManager, local, remote endpoint) *LocalConn { - return &LocalConn{ - remote: remote, - local: local, - connQueue: newConnQueue(), - manager: lm, + lc := &LocalConn{ + remote: remote, + local: local, + manager: lm, + incomingQueue: make(chan []byte, LocalMaxBuffer), + outgoingQueue: make(chan []byte, LocalMaxBuffer), + closeCh: make(chan bool), + closeConfirm: make(chan bool), } + go lc.start() + return lc } // NewLocalConn returns a new channel connection from local to remote. @@ -214,6 +228,21 @@ func NewLocalConnWithManager(lm *LocalManager, local, remote Address) (*LocalCon return nil, errors.New("Could not connect") } +func (lc *LocalConn) start() { + for { + select { + case buff := <-lc.incomingQueue: + lc.outgoingQueue <- buff + case <-lc.closeCh: + // to signal that the conn is closed + close(lc.outgoingQueue) + close(lc.incomingQueue) + lc.closeConfirm <- true + return + } + } +} + // Send takes a context (that is not used in any way) and a message that // will be sent to the remote endpoint. // If there is an error in the connection, it will be returned. @@ -230,9 +259,9 @@ func (lc *LocalConn) Send(msg Body) error { // be ready. It returns the received packet. // In case of an error the packet is nil and the error is returned. func (lc *LocalConn) Receive() (Packet, error) { - buff, err := lc.pop() - if err != nil { - return EmptyApplicationPacket, err + buff, opened := <-lc.outgoingQueue + if !opened { + return EmptyApplicationPacket, ErrClosed } lc.updateRx(uint64(len(buff))) @@ -257,14 +286,20 @@ func (lc *LocalConn) Remote() Address { // side. // If the connection is not open, it returns an error. func (lc *LocalConn) Close() error { - if lc.connQueue.isClosed() { - return ErrClosed + select { + case _, o := <-lc.closeCh: + if !o { + return ErrClosed + } + default: } + return lc.manager.close(lc) +} - lc.connQueue.close() - // close the remote conn also - lc.manager.close(lc) - return nil +func (lc *LocalConn) closeChannels() { + close(lc.closeCh) + <-lc.closeConfirm + close(lc.closeConfirm) } // Type implements the Conn interface @@ -277,66 +312,12 @@ func (lc *LocalConn) Type() ConnType { // All operations are thread-safe. // The messages are marshalled and stored in the queue as a slice of bytes. type connQueue struct { - *sync.Cond - queue [][]byte - closed bool -} - -func newConnQueue() *connQueue { - return &connQueue{ - Cond: sync.NewCond(&sync.Mutex{}), - } + wg sync.WaitGroup } -// push inserts a packet in the queue. -// push won't work if the connQueue is already closed and silently return. -func (c *connQueue) push(buff []byte) { - c.L.Lock() - defer c.L.Unlock() - if c.closed { - return - } - c.queue = append(c.queue, buff) - c.Signal() -} - -// pop retrieves a packet out of the queue. -// If there is no message, then it blocks UNTIL there is a call to push() or -// close(). -// pop returns with an error if the queue is closed or gets closed while waiting -// for a packet. -func (c *connQueue) pop() ([]byte, error) { - c.L.Lock() - defer c.L.Unlock() - for len(c.queue) == 0 { - if c.closed { - return nil, ErrClosed - } - c.Wait() - } - if c.closed { - return nil, ErrClosed - } - nm := c.queue[0] - c.queue = c.queue[1:] - return nm, nil -} - -// close sets the closed-field to true and signals to all ongoing pop() -// operations to return. -func (c *connQueue) close() { - c.L.Lock() - defer c.L.Unlock() - c.closed = true - c.Broadcast() -} - -// isClosed returns whether this queue is closed or not. -func (c *connQueue) isClosed() bool { - c.L.Lock() - defer c.L.Unlock() - return c.closed -} +// LocalMaxBuffer is the number of packets that can be sent simultaneously to the +// same address. +const LocalMaxBuffer = 200 // LocalListener implements Listener and uses LocalConn to communicate. It // behaves as much as possible as a real golang net.Listener but using LocalConn diff --git a/network/local_test.go b/network/local_test.go index e1f0fd62..ca201a2d 100644 --- a/network/local_test.go +++ b/network/local_test.go @@ -80,6 +80,7 @@ func TestLocalConnCloseReceive(t *testing.T) { listener.Listen(func(c Conn) { ready <- true assert.Nil(t, c.Close()) + ready <- true }) }() <-ready @@ -95,12 +96,11 @@ func TestLocalConnCloseReceive(t *testing.T) { t.Error("Wrong local addr for Conn!?") } <-ready - + <-ready _, err = outgoing.Receive() assert.Equal(t, ErrClosed, err) assert.Equal(t, ErrClosed, outgoing.Close()) assert.Nil(t, listener.Stop()) - } // Test if we can run two parallel local network using two different contexts @@ -209,7 +209,6 @@ func testConnListener(ctx *LocalManager, done chan error, listenA, connA *Server done <- err return } - listener.Stop() <-ok done <- nil @@ -251,6 +250,8 @@ func testLocalConn(t *testing.T, a1, a2 Address) { assert.Equal(t, 2, listener.manager.len()) // close connection assert.Nil(t, c.Close()) + incomingConn <- true + }) ready <- true }() @@ -273,13 +274,13 @@ func testLocalConn(t *testing.T, a1, a2 Address) { assert.Equal(t, 3, nm.Msg.(SimpleMessage).I) outgoingConn <- true + <-incomingConn // close the incoming conn, so Receive here should return an error nm, err = outgoing.Receive() if err != ErrClosed { t.Error("Receive should have returned an error") } assert.Equal(t, ErrClosed, outgoing.Close()) - // close the listener assert.Nil(t, listener.Stop()) <-ready diff --git a/network/router.go b/network/router.go index 5f6aa0cd..c1b4a394 100644 --- a/network/router.go +++ b/network/router.go @@ -34,7 +34,7 @@ type Router struct { // can be opened at the same time on both endpoints, there can be more // than one connection per ServerIdentityID. connections map[ServerIdentityID][]Conn - connsMut sync.Mutex + sync.Mutex // boolean flag indicating that the router is already clos{ing,ed}. isClosed bool @@ -71,9 +71,16 @@ func (r *Router) Start() { } return } - // start handleConn that waits for incoming messages and + if err := r.registerConnection(dst, c); err != nil { + log.Lvl3(r.address, "does not accept incoming connection to", c.Remote(), "because it's closed") + return + } + // start handleConn in a go routine that waits for incoming messages and // dispatches them. - r.launchHandleRoutine(dst, c) + if err := r.launchHandleRoutine(dst, c); err != nil { + log.Lvl3(r.address, "does not accept incoming connection to", c.Remote(), "because it's closed") + return + } }) if err != nil { log.Error("Error listening:", err) @@ -86,10 +93,8 @@ func (r *Router) Start() { // Router. func (r *Router) Stop() error { var err error - //if r.host.Listening() { err = r.host.Stop() - //} - r.connsMut.Lock() + r.Lock() // set the isClosed to true r.isClosed = true @@ -102,14 +107,10 @@ func (r *Router) Stop() error { } } } - r.connsMut.Unlock() - // wait for all handleConn to finish + r.Unlock() r.wg.Wait() - r.connsMut.Lock() - r.isClosed = false - r.connsMut.Unlock() if err != nil { return err } @@ -165,13 +166,39 @@ func (r *Router) connect(si *ServerIdentity) (Conn, error) { return nil, err } - r.registerConnection(si, c) + if err := r.registerConnection(si, c); err != nil { + return nil, err + } - r.launchHandleRoutine(si, c) + if err := r.launchHandleRoutine(si, c); err != nil { + return nil, err + } return c, nil } +func (r *Router) removeConnection(si *ServerIdentity, c Conn) { + r.Lock() + defer r.Unlock() + + var toDelete = -1 + arr := r.connections[si.ID] + for i, cc := range arr { + if c == cc { + toDelete = i + } + } + + if toDelete == -1 { + log.Error("Remove a connection which is not registered !?") + return + } + + arr[toDelete] = arr[len(arr)-1] + arr[len(arr)-1] = nil + r.connections[si.ID] = arr[:len(arr)-1] +} + // handleConn waits for incoming messages and calls the dispatcher for // each new message. It only quits if the connection is closed or another // unrecoverable error in the connection appears. @@ -182,6 +209,7 @@ func (r *Router) handleConn(remote *ServerIdentity, c Conn) { log.Lvl5(r.address, "having error closing conn to", remote.Address, ":", err) } r.wg.Done() + r.removeConnection(remote, c) }() address := c.Remote() log.Lvl3(r.address, "Handling new connection to", remote.Address) @@ -193,11 +221,14 @@ func (r *Router) handleConn(remote *ServerIdentity, c Conn) { } if err != nil { - log.Lvlf4("%+v got error (%+s) while receiving message", r.ServerIdentity.String(), err) + if err == ErrTimeout { + log.Lvlf5("%s drops %s connection: timeout", r.ServerIdentity.Address, remote.Address) + return + } if err == ErrClosed || err == ErrEOF { // Connection got closed. - log.Lvl3(r.address, "handleConn with closed connection: stop (dst=", remote.Address, ")") + log.Lvlf5("%s drops %s connection: closed", r.ServerIdentity.Address, remote.Address) return } // Temporary error, continue. @@ -218,8 +249,8 @@ func (r *Router) handleConn(remote *ServerIdentity, c Conn) { // connection returns the first connection associated with this ServerIdentity. // If no connection is found, it returns nil. func (r *Router) connection(sid ServerIdentityID) Conn { - r.connsMut.Lock() - defer r.connsMut.Unlock() + r.Lock() + defer r.Unlock() arr := r.connections[sid] if len(arr) == 0 { return nil @@ -230,35 +261,45 @@ func (r *Router) connection(sid ServerIdentityID) Conn { // registerConnection registers a ServerIdentity for a new connection, mapped with the // real physical address of the connection and the connection itself. // It uses the networkLock mutex. -func (r *Router) registerConnection(remote *ServerIdentity, c Conn) { +func (r *Router) registerConnection(remote *ServerIdentity, c Conn) error { log.Lvl4(r.address, "Registers", remote.Address) - r.connsMut.Lock() - defer r.connsMut.Unlock() + r.Lock() + defer r.Unlock() + if r.isClosed { + return ErrClosed + } _, okc := r.connections[remote.ID] if okc { log.Lvl5("Connection already registered. Appending new connection to same identity.") } r.connections[remote.ID] = append(r.connections[remote.ID], c) + return nil } -func (r *Router) launchHandleRoutine(dst *ServerIdentity, c Conn) { +func (r *Router) launchHandleRoutine(dst *ServerIdentity, c Conn) error { + r.Lock() + defer r.Unlock() + if r.isClosed { + return ErrClosed + } r.wg.Add(1) go r.handleConn(dst, c) + return nil } // Closed returns true if the router is closed (or is closing). For a router // to be closed means that a call to Stop() must have been made. func (r *Router) Closed() bool { - r.connsMut.Lock() - defer r.connsMut.Unlock() + r.Lock() + defer r.Unlock() return r.isClosed } // Tx implements monitor/CounterIO // It returns the Tx for all connections managed by this router func (r *Router) Tx() uint64 { - r.connsMut.Lock() - defer r.connsMut.Unlock() + r.Lock() + defer r.Unlock() var tx uint64 for _, arr := range r.connections { for _, c := range arr { @@ -271,8 +312,8 @@ func (r *Router) Tx() uint64 { // Rx implements monitor/CounterIO // It returns the Rx for all connections managed by this router func (r *Router) Rx() uint64 { - r.connsMut.Lock() - defer r.connsMut.Unlock() + r.Lock() + defer r.Unlock() var rx uint64 for _, arr := range r.connections { for _, c := range arr { @@ -307,6 +348,5 @@ func (r *Router) receiveServerIdentity(c Conn) (*ServerIdentity, error) { return nil, err } log.Lvl4(r.address, "Identity received from", dst.Address) - r.registerConnection(&dst, c) return &dst, nil } diff --git a/network/router_test.go b/network/router_test.go index 52545fcf..9ffcb0d6 100644 --- a/network/router_test.go +++ b/network/router_test.go @@ -63,6 +63,30 @@ func testRouter(t *testing.T, fac routerFactory) { } } +func testRouterRemoveConnection(t *testing.T) { + r1, err := NewTestRouterTCP(2008) + require.Nil(t, err) + r2, err := NewTestRouterTCP(2009) + require.Nil(t, err) + + defer r1.Stop() + + go r1.Start() + go r2.Start() + + require.NotNil(t, r1.Send(r2.ServerIdentity, nil)) + + r1.Lock() + require.Equal(t, 1, len(r1.connections[r2.ServerIdentity.ID])) + r1.Unlock() + + require.Nil(t, r2.Stop()) + + r1.Lock() + require.Equal(t, 0, len(r1.connections[r2.ServerIdentity.ID])) + r1.Unlock() +} + // Test the automatic connection upon request func TestRouterAutoConnectionTCP(t *testing.T) { testRouterAutoConnection(t, NewTestRouterTCP) @@ -117,14 +141,17 @@ func testRouterAutoConnection(t *testing.T, fac routerFactory) { assert.NotNil(t, h12) require.NotNil(t, h21) assert.Nil(t, h21.Close()) + time.Sleep(100 * time.Millisecond) + err = h1.Send(h2.ServerIdentity, &SimpleMessage{12}) + require.Nil(t, err) + <-proc.relay + if err := h2.Stop(); err != nil { t.Fatal("Should be able to stop h2") } - h2.connsMut.Lock() - delete(h2.connections, h1.ServerIdentity.ID) - h2.connsMut.Unlock() err = h1.Send(h2.ServerIdentity, &SimpleMessage{12}) require.NotNil(t, err) + } // Test connection of multiple Hosts and sending messages back and forth @@ -172,47 +199,64 @@ func TestRouterMessaging(t *testing.T) { } func TestRouterLotsOfConnTCP(t *testing.T) { - testRouterLotsOfConn(t, NewTestRouterTCP) + testRouterLotsOfConn(t, NewTestRouterTCP, 5) } func TestRouterLotsOfConnLocal(t *testing.T) { - testRouterLotsOfConn(t, NewTestRouterLocal) + testRouterLotsOfConn(t, NewTestRouterLocal, 5) } // nSquareProc will send back all packet sent and stop when it has received // enough, it releases the waitgroup. type nSquareProc struct { - t *testing.T - r *Router - expected int - actual int - wg *sync.WaitGroup + t *testing.T + r *Router + expected int + wg *sync.WaitGroup + firstRound map[Address]bool + secondRound map[Address]bool + sync.Mutex } func newNSquareProc(t *testing.T, r *Router, expect int, wg *sync.WaitGroup) *nSquareProc { - return &nSquareProc{t, r, expect, 0, wg} + return &nSquareProc{t, r, expect, wg, make(map[Address]bool), make(map[Address]bool), sync.Mutex{}} } func (p *nSquareProc) Process(pack *Packet) { - p.actual++ - if p.actual == p.expected { - // release - p.wg.Done() + p.Lock() + defer p.Unlock() + remote := pack.ServerIdentity.Address + ok := p.firstRound[remote] + if ok { + // second round + if ok := p.secondRound[remote]; ok { + p.t.Fatal("Already received second round") + } + p.secondRound[remote] = true + + if len(p.secondRound) == p.expected { + // release + p.wg.Done() + } return - } else if p.actual > p.expected { - p.t.Fatal("Too many response ??") } - msg := pack.Msg.(SimpleMessage) - p.r.Send(pack.ServerIdentity, &msg) + + p.firstRound[remote] = true + if err := p.r.Send(pack.ServerIdentity, &SimpleMessage{3}); err != nil { + p.t.Fatal("Could not send to first round dest.") + } + } // Makes a big mesh where every host send and receive to every other hosts -func testRouterLotsOfConn(t *testing.T, fac routerFactory) { - nbrRouter := 2 +func testRouterLotsOfConn(t *testing.T, fac routerFactory, nbrRouter int) { // create all the routers routers := make([]*Router, nbrRouter) + // to wait for the creation of all hosts var wg1 sync.WaitGroup wg1.Add(nbrRouter) + var wg2 sync.WaitGroup + wg2.Add(nbrRouter) for i := 0; i < nbrRouter; i++ { go func(j int) { r, err := fac(2000 + j) @@ -224,19 +268,17 @@ func testRouterLotsOfConn(t *testing.T, fac routerFactory) { time.Sleep(20 * time.Millisecond) } routers[j] = r + // expect nbrRouter - 1 messages + proc := newNSquareProc(t, r, nbrRouter-1, &wg2) + r.RegisterProcessor(proc, SimpleMessageType) wg1.Done() }(i) } wg1.Wait() - var wg2 sync.WaitGroup - wg2.Add(nbrRouter) for i := 0; i < nbrRouter; i++ { go func(j int) { r := routers[j] - // expect nbrRouter - 1 messages - proc := newNSquareProc(t, r, nbrRouter-1, &wg2) - r.RegisterProcessor(proc, SimpleMessageType) for k := 0; k < nbrRouter; k++ { if k == j { // don't send to yourself diff --git a/network/struct.go b/network/struct.go index 7884a71d..76178f03 100644 --- a/network/struct.go +++ b/network/struct.go @@ -75,6 +75,8 @@ type ServerIdentity struct { ID ServerIdentityID // A slice of addresses of where that Id might be found Address Address + // Description of the server + Description string } // ServerIdentityID uniquely identifies an ServerIdentity struct diff --git a/network/tcp.go b/network/tcp.go index 7c6f56a8..0367927a 100644 --- a/network/tcp.go +++ b/network/tcp.go @@ -14,6 +14,10 @@ import ( "github.com/dedis/cothority/log" ) +// a connection will return an io.EOF after readTimeout if nothing has been +// sent. +var readTimeout = 1 * time.Minute + // NewTCPRouter returns a new Router using TCPHost as the underlying Host. func NewTCPRouter(sid *ServerIdentity) (*Router, error) { h, err := NewTCPHost(sid.Address) @@ -73,7 +77,7 @@ func NewTCPConn(addr Address) (conn *TCPConn, err error) { func (c *TCPConn) Receive() (nm Packet, e error) { defer func() { if err := recover(); err != nil { - e = fmt.Errorf("Error Received message: %v", err) + e = fmt.Errorf("Error Received message: %v\n%s", err, log.Stack()) nm = EmptyApplicationPacket } }() @@ -100,16 +104,19 @@ func (c *TCPConn) Receive() (nm Packet, e error) { func (c *TCPConn) receiveRaw() ([]byte, error) { c.receiveMutex.Lock() defer c.receiveMutex.Unlock() + c.conn.SetReadDeadline(time.Now().Add(readTimeout)) // First read the size var total Size if err := binary.Read(c.conn, globalOrder, &total); err != nil { return nil, handleError(err) } + b := make([]byte, total) var read Size var buffer bytes.Buffer for read < total { // Read the size of the next packet. + c.conn.SetReadDeadline(time.Now().Add(readTimeout)) n, err := c.conn.Read(b) // Quit if there is an error. if err != nil { diff --git a/network/tcp_test.go b/network/tcp_test.go index 0f236225..30f6b13f 100644 --- a/network/tcp_test.go +++ b/network/tcp_test.go @@ -12,6 +12,7 @@ import ( "github.com/dedis/cothority/log" "github.com/dedis/crypto/config" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -243,6 +244,56 @@ func TestTCPConn(t *testing.T) { <-done } +func TestTCPConnTimeout(t *testing.T) { + tmp := readTimeout + readTimeout = 100 * time.Millisecond + defer func() { readTimeout = tmp }() + + addr := NewTCPAddress("127.0.0.1:5678") + ln, err := NewTCPListener(addr) + if err != nil { + t.Fatal("error setup listener", err) + } + ready := make(chan bool) + connStat := make(chan error) + + connFn := func(c Conn) { + // receive first a good packet + _, err := c.Receive() + connStat <- err + // then this receive should throw out the error + _, err = c.Receive() + connStat <- err + } + go func() { + ready <- true + err := ln.Listen(connFn) + require.Nil(t, err, "Listener stop incorrectly") + }() + + <-ready + c, err := NewTCPConn(addr) + require.Nil(t, err, "Could not open connection") + // Test bandwitdth measurements also + require.Nil(t, c.Send(&SimpleMessage{3})) + select { + case received := <-connStat: + assert.Nil(t, received) + case <-time.After(readTimeout + 100*time.Millisecond): + t.Error("Did not received message after timeout...") + } + + select { + case received := <-connStat: + assert.NotNil(t, received) + case <-time.After(readTimeout + 100*time.Millisecond): + t.Error("Did not received message after timeout...") + } + + assert.Nil(t, c.Close()) + assert.Nil(t, ln.Stop()) +} + func TestTCPConnWithListener(t *testing.T) { addr := NewTCPAddress("127.0.0.1:5678") ln, err := NewTCPListener(addr) diff --git a/protocols/bftcosi/packets.go b/protocols/bftcosi/packets.go index ab5e329d..4e927818 100644 --- a/protocols/bftcosi/packets.go +++ b/protocols/bftcosi/packets.go @@ -4,10 +4,24 @@ import ( "crypto/sha512" "errors" + "github.com/dedis/cothority/network" "github.com/dedis/cothority/sda" "github.com/dedis/crypto/abstract" ) +func init() { + for _, i := range []interface{}{ + BFTSignature{}, + Announce{}, + Commitment{}, + ChallengePrepare{}, + ChallengeCommit{}, + Response{}, + } { + network.RegisterPacketType(i) + } +} + // RoundType is a type to know if we are in the "prepare" round or the "commit" // round type RoundType int32 diff --git a/protocols/byzcoin/cosi/cosi.go b/protocols/byzcoin/cosi/cosi.go index f6c8a116..4e683265 100644 --- a/protocols/byzcoin/cosi/cosi.go +++ b/protocols/byzcoin/cosi/cosi.go @@ -36,10 +36,22 @@ import ( "errors" "time" + "github.com/dedis/cothority/network" "github.com/dedis/crypto/abstract" "github.com/dedis/crypto/config" ) +func init() { + for _, r := range []interface{}{ + Announcement{}, + Commitment{}, + Challenge{}, + Response{}, + } { + network.RegisterPacketType(r) + } +} + // Cosi is the struct that implements the basic cosi. type Cosi struct { // Suite used diff --git a/protocols/byzcoin/ntree/ntree.go b/protocols/byzcoin/ntree/ntree.go index 5ebf8983..18568560 100644 --- a/protocols/byzcoin/ntree/ntree.go +++ b/protocols/byzcoin/ntree/ntree.go @@ -6,12 +6,26 @@ import ( "github.com/dedis/cothority/crypto" "github.com/dedis/cothority/log" + "github.com/dedis/cothority/network" "github.com/dedis/cothority/protocols/byzcoin" "github.com/dedis/cothority/protocols/byzcoin/blockchain" "github.com/dedis/cothority/protocols/byzcoin/blockchain/blkparser" "github.com/dedis/cothority/sda" ) +func init() { + for _, i := range []interface{}{ + BlockAnnounce{}, + NaiveBlockSignature{}, + Exception{}, + RoundSignatureRequest{}, + RoundSignatureResponse{}, + NtreeSignature{}, + } { + network.RegisterPacketType(i) + } +} + // Ntree is a basic implementation of a byzcoin consensus protocol using a tree // and each verifiers will have independent signatures. The messages are then // bigger and the verification time is also longer. diff --git a/protocols/byzcoin/packets.go b/protocols/byzcoin/packets.go index 66ed2dc4..09643777 100644 --- a/protocols/byzcoin/packets.go +++ b/protocols/byzcoin/packets.go @@ -1,11 +1,25 @@ package byzcoin import ( + "github.com/dedis/cothority/network" "github.com/dedis/cothority/protocols/byzcoin/blockchain" "github.com/dedis/cothority/protocols/byzcoin/cosi" "github.com/dedis/cothority/sda" ) +func init() { + for _, i := range []interface{}{ + BlockSignature{}, + Announce{}, + Commitment{}, + ChallengePrepare{}, + ChallengeCommit{}, + Response{}, + } { + network.RegisterPacketType(i) + } +} + // RoundType is a type to know if we are in the "prepare" round or the "commit" // round type RoundType int32 diff --git a/protocols/byzcoin/pbft/packets.go b/protocols/byzcoin/pbft/packets.go index 8e127ef9..f130cdfc 100644 --- a/protocols/byzcoin/pbft/packets.go +++ b/protocols/byzcoin/pbft/packets.go @@ -1,10 +1,22 @@ package pbft import ( + "github.com/dedis/cothority/network" "github.com/dedis/cothority/protocols/byzcoin/blockchain" "github.com/dedis/cothority/sda" ) +func init() { + for _, i := range []interface{}{ + PrePrepare{}, + Prepare{}, + Commit{}, + Finish{}, + } { + network.RegisterPacketType(i) + } +} + // Messages which will be sent around by the most naive PBFT simulation in // "byzcoin" diff --git a/protocols/cosi/cosi.go b/protocols/cosi/cosi.go new file mode 100644 index 00000000..661777ff --- /dev/null +++ b/protocols/cosi/cosi.go @@ -0,0 +1,337 @@ +// Package cosi implements a round of a Collective Signing protocol. +package cosi + +import ( + "sync" + + "github.com/dedis/cothority/log" + "github.com/dedis/cothority/sda" + "github.com/dedis/crypto/abstract" + "github.com/dedis/crypto/cosi" +) + +// Name can be used to reference the registered protocol. +var Name = "CoSi" + +func init() { + sda.GlobalProtocolRegister(Name, NewProtocol) +} + +// This Cosi protocol is the simplest version, the "vanilla" version with the +// four phases: +// - Announcement +// - Commitment +// - Challenge +// - Response + +// CoSi is the main structure holding the round and the sda.Node. +type CoSi struct { + // The node that represents us + *sda.TreeNodeInstance + // TreeNodeId cached + treeNodeID sda.TreeNodeID + // the cosi struct we use (since it is a cosi protocol) + // Public because we will need it from other protocols. + cosi *cosi.CoSi + // the message we want to sign typically given by the Root + Message []byte + // The channel waiting for Announcement message + announce chan chanAnnouncement + // the channel waiting for Commitment message + commit chan chanCommitment + // the channel waiting for Challenge message + challenge chan chanChallenge + // the channel waiting for Response message + response chan chanResponse + // the channel that indicates if we are finished or not + done chan bool + // temporary buffer of commitment messages + tempCommitment []abstract.Point + // lock associated + tempCommitLock *sync.Mutex + // temporary buffer of Response messages + tempResponse []abstract.Scalar + // lock associated + tempResponseLock *sync.Mutex + + // hooks related to the various phase of the protocol. + announcementHook AnnouncementHook + commitmentHook CommitmentHook + challengeHook ChallengeHook + responseHook ResponseHook + signatureHook SignatureHook +} + +// AnnouncementHook allows for handling what should happen upon an +// announcement +type AnnouncementHook func() error + +// CommitmentHook allows for handling what should happen when all +// commitments are received +type CommitmentHook func(in []abstract.Point) error + +// ChallengeHook allows for handling what should happen when a +// challenge is received +type ChallengeHook func(ch abstract.Scalar) error + +// ResponseHook allows for handling what should happen when all +// responses are received and our response is calculated +type ResponseHook func(in []abstract.Scalar) + +// SignatureHook allows registering a handler when the signature is done +type SignatureHook func(sig []byte) + +// NewProtocol returns a ProtocolCosi with the node set with the right channels. +// Use this function like this: +// ``` +// round := NewRound****() +// fn := func(n *sda.Node) sda.ProtocolInstance { +// pc := NewProtocolCosi(round,n) +// return pc +// } +// sda.RegisterNewProtocolName("cothority",fn) +// ``` +func NewProtocol(node *sda.TreeNodeInstance) (sda.ProtocolInstance, error) { + var err error + // XXX just need to take care to take the global list of cosigners once we + // do the exception stuff + publics := make([]abstract.Point, len(node.Roster().List)) + for i, e := range node.Roster().List { + publics[i] = e.Public + } + + c := &CoSi{ + cosi: cosi.NewCosi(node.Suite(), node.Private(), publics), + TreeNodeInstance: node, + done: make(chan bool), + tempCommitLock: new(sync.Mutex), + tempResponseLock: new(sync.Mutex), + } + // Register the channels we want to register and listens on + + if err := node.RegisterChannel(&c.announce); err != nil { + return c, err + } + if err := node.RegisterChannel(&c.commit); err != nil { + return c, err + } + if err := node.RegisterChannel(&c.challenge); err != nil { + return c, err + } + if err := node.RegisterChannel(&c.response); err != nil { + return c, err + } + + return c, err +} + +// Dispatch will listen on the four channels we use (i.e. four steps) +func (c *CoSi) Dispatch() error { + for { + var err error + select { + case packet := <-c.announce: + err = c.handleAnnouncement(&packet.Announcement) + case packet := <-c.commit: + err = c.handleCommitment(&packet.Commitment) + case packet := <-c.challenge: + err = c.handleChallenge(&packet.Challenge) + case packet := <-c.response: + err = c.handleResponse(&packet.Response) + case <-c.done: + return nil + } + if err != nil { + log.Error("ProtocolCosi -> err treating incoming:", err) + } + } +} + +// Start will call the announcement function of its inner Round structure. It +// will pass nil as *in* message. +func (c *CoSi) Start() error { + out := &Announcement{} + return c.handleAnnouncement(out) +} + +// VerifySignature verifies if the challenge and the secret (from the response phase) form a +// correct signature for this message using the aggregated public key. +// This is copied from cosi, so that you don't need to include both lib/cosi +// and protocols/cosi +func VerifySignature(suite abstract.Suite, publics []abstract.Point, msg, sig []byte) error { + return cosi.VerifySignature(suite, publics, msg, sig) +} + +// handleAnnouncement will pass the message to the round and send back the +// output. If in == nil, we are root and we start the round. +func (c *CoSi) handleAnnouncement(in *Announcement) error { + log.Lvlf3("Message: %x", c.Message) + // If we have a hook on announcement call the hook + if c.announcementHook != nil { + return c.announcementHook() + } + + // If we are leaf, we should go to commitment + if c.IsLeaf() { + return c.handleCommitment(nil) + } + // send to children + return c.SendToChildren(in) +} + +// handleAllCommitment relay the commitments up in the tree +// It expects *in* to be the full set of messages from the children. +// The children's commitment must remain constants. +func (c *CoSi) handleCommitment(in *Commitment) error { + if !c.IsLeaf() { + // add to temporary + c.tempCommitLock.Lock() + c.tempCommitment = append(c.tempCommitment, in.Comm) + c.tempCommitLock.Unlock() + // do we have enough ? + // TODO: exception mechanism will be put into another protocol + if len(c.tempCommitment) < len(c.Children()) { + return nil + } + } + log.Lvl3(c.Name(), "aggregated") + // pass it to the hook + if c.commitmentHook != nil { + return c.commitmentHook(c.tempCommitment) + } + + // go to Commit() + out := c.cosi.Commit(nil, c.tempCommitment) + + // if we are the root, we need to start the Challenge + if c.IsRoot() { + return c.startChallenge() + } + + // otherwise send it to parent + outMsg := &Commitment{ + Comm: out, + } + return c.SendTo(c.Parent(), outMsg) +} + +// StartChallenge starts the challenge phase. Typically called by the Root ;) +func (c *CoSi) startChallenge() error { + challenge, err := c.cosi.CreateChallenge(c.Message) + if err != nil { + return err + } + out := &Challenge{ + Chall: challenge, + } + log.Lvlf3("%s Starting Chal=%+v (message = %x)", c.Name(), challenge, c.Message) + return c.handleChallenge(out) + +} + +// handleChallenge dispatch the challenge to the round and then dispatch the +// results down the tree. +func (c *CoSi) handleChallenge(in *Challenge) error { + log.Lvlf3("%s chal=%+v", c.Name(), in.Chall) + c.cosi.Challenge(in.Chall) + + if c.challengeHook != nil { + c.challengeHook(in.Chall) + } + + // if we are leaf, then go to response + if c.IsLeaf() { + return c.handleResponse(nil) + } + + // otherwise send it to children + return c.SendToChildren(in) +} + +// handleResponse brings up the response of each node in the tree to the root. +func (c *CoSi) handleResponse(in *Response) error { + if !c.IsLeaf() { + // add to temporary + c.tempResponseLock.Lock() + c.tempResponse = append(c.tempResponse, in.Resp) + c.tempResponseLock.Unlock() + // do we have enough ? + log.Lvl3(c.Name(), "has", len(c.tempResponse), "responses") + if len(c.tempResponse) < len(c.Children()) { + return nil + } + } + + defer func() { + // protocol is finished + close(c.done) + c.Done() + }() + + log.Lvl3(c.Name(), "aggregated") + outResponse, err := c.cosi.Response(c.tempResponse) + if err != nil { + return err + } + + if c.responseHook != nil { + c.responseHook(c.tempResponse) + } + + out := &Response{ + Resp: outResponse, + } + + // send it back to parent + if !c.IsRoot() { + return c.SendTo(c.Parent(), out) + } + + // we are root, we have the signature now + if c.signatureHook != nil { + c.signatureHook(c.cosi.Signature()) + } + return nil +} + +// VerifyResponses allows to check at each intermediate node whether the +// responses are valid +func (c *CoSi) VerifyResponses(agg abstract.Point) error { + return c.cosi.VerifyResponses(agg) +} + +// SigningMessage simply set the message to sign for this round +func (c *CoSi) SigningMessage(msg []byte) { + c.Message = msg + log.Lvlf2("%s Root will sign message %x", c.Name(), c.Message) +} + +// RegisterAnnouncementHook allows for handling what should happen upon an +// announcement +func (c *CoSi) RegisterAnnouncementHook(fn AnnouncementHook) { + c.announcementHook = fn +} + +// RegisterCommitmentHook allows for handling what should happen when a +// commitment is received +func (c *CoSi) RegisterCommitmentHook(fn CommitmentHook) { + c.commitmentHook = fn +} + +// RegisterChallengeHook allows for handling what should happen when a +// challenge is received +func (c *CoSi) RegisterChallengeHook(fn ChallengeHook) { + c.challengeHook = fn +} + +// RegisterResponseHook allows for handling what should happen when a +// response is received +func (c *CoSi) RegisterResponseHook(fn ResponseHook) { + c.responseHook = fn +} + +// RegisterSignatureHook allows for handling what should happen when +// the protocol is done +func (c *CoSi) RegisterSignatureHook(fn SignatureHook) { + c.signatureHook = fn +} diff --git a/protocols/cosi/cosi_test.go b/protocols/cosi/cosi_test.go new file mode 100644 index 00000000..5c5843a3 --- /dev/null +++ b/protocols/cosi/cosi_test.go @@ -0,0 +1,67 @@ +package cosi + +import ( + "testing" + "time" + + "github.com/dedis/cothority/log" + "github.com/dedis/cothority/network" + "github.com/dedis/cothority/sda" + "github.com/dedis/crypto/abstract" +) + +func TestCosi(t *testing.T) { + //defer log.AfterTest(t) + log.TestOutput(testing.Verbose(), 4) + for _, nbrHosts := range []int{1, 3, 13} { + log.Lvl2("Running cosi with", nbrHosts, "hosts") + local := sda.NewLocalTest() + hosts, el, tree := local.GenBigTree(nbrHosts, nbrHosts, 3, true) + aggPublic := network.Suite.Point().Null() + for _, e := range el.List { + aggPublic = aggPublic.Add(aggPublic, e.Public) + } + + done := make(chan bool) + // create the message we want to sign for this round + msg := []byte("Hello World Cosi") + + // Register the function generating the protocol instance + var root *CoSi + // function that will be called when protocol is finished by the root + doneFunc := func(sig []byte) { + suite := hosts[0].Suite() + publics := el.Publics() + if err := root.VerifyResponses(aggPublic); err != nil { + t.Fatal("Error verifying responses", err) + } + if err := VerifySignature(suite, publics, msg, sig); err != nil { + t.Fatal("Error verifying signature:", err) + } + done <- true + } + + // Start the protocol + p, err := local.CreateProtocol("CoSi", tree) + if err != nil { + t.Fatal("Couldn't create new node:", err) + } + root = p.(*CoSi) + root.Message = msg + responseFunc := func(in []abstract.Scalar) { + log.Lvl1("Got response") + if len(root.Children()) != len(in) { + t.Fatal("Didn't get same number of responses") + } + } + root.RegisterResponseHook(responseFunc) + root.RegisterSignatureHook(doneFunc) + go root.StartProtocol() + select { + case <-done: + case <-time.After(time.Second * 2): + t.Fatal("Could not get signature verification done in time") + } + local.CloseAll() + } +} diff --git a/protocols/cosi/packets.go b/protocols/cosi/packets.go new file mode 100644 index 00000000..dd78c5f8 --- /dev/null +++ b/protocols/cosi/packets.go @@ -0,0 +1,155 @@ +package cosi + +import ( + "errors" + + "github.com/dedis/cothority/network" + "github.com/dedis/cothority/sda" + "github.com/dedis/crypto/abstract" +) + +func init() { + sda.RegisterMessageProxy(func() sda.MessageProxy { + return new(MessageProxy) + }) + for _, r := range []interface{}{ + Announcement{}, + Commitment{}, + Challenge{}, + Response{}, + } { + network.RegisterPacketType(r) + } +} + +const ( + // AnnouncementPhase is the ID of the Announcement message + AnnouncementPhase uint32 = 1 + // CommitmentPhase is the ID of the Commitment message + CommitmentPhase = 2 + // ChallengePhase is the ID of the Challenge message + ChallengePhase = 3 + // ResponsePhase is the ID of the Response message + ResponsePhase = 4 +) + +// ProtocolPacketID is the network.PacketTypeID of the CoSi ProtocolPacket +var ProtocolPacketID = network.RegisterPacketType(ProtocolPacket{}) + +// ProtocolPacket is the main message for the CoSi protocol which includes +// every information that the CoSi protocol might need. +type ProtocolPacket struct { + Phase uint32 + + OverlayMessage *sda.OverlayMessage + + Ann *Announcement + Comm *Commitment + Chal *Challenge + Resp *Response +} + +// MessageProxy implements the sda.MessageProxy interface for the CoSi protocol. +type MessageProxy struct{} + +// Wrap implements the sda.MessageProxy interface by wrapping up any of the +// four-step messages into a ProtooclPacket. +func (p *MessageProxy) Wrap(msg interface{}, info *sda.OverlayMessage) (interface{}, error) { + var packet = new(ProtocolPacket) + packet.OverlayMessage = info + + switch inner := msg.(type) { + case *Announcement: + packet.Ann = inner + packet.Phase = AnnouncementPhase + case *Commitment: + packet.Comm = inner + packet.Phase = CommitmentPhase + case *Challenge: + packet.Chal = inner + packet.Phase = ChallengePhase + case *Response: + packet.Resp = inner + packet.Phase = ResponsePhase + } + + return packet, nil +} + +// Unwrap implements the sda.MessageProxy interface by unwraping and returning the +// specific message of one of the four steps. +func (p *MessageProxy) Unwrap(msg interface{}) (interface{}, *sda.OverlayMessage, error) { + var inner interface{} + packet, ok := msg.(ProtocolPacket) + if !ok { + return nil, nil, errors.New("cosi protocolio: unknown packet to unwrap") + } + + if packet.OverlayMessage == nil { + return nil, nil, errors.New("cosi protocolio: no overlay information given") + } + + switch packet.Phase { + case AnnouncementPhase: + inner = packet.Ann + case CommitmentPhase: + inner = packet.Comm + case ChallengePhase: + inner = packet.Chal + case ResponsePhase: + inner = packet.Resp + } + return inner, packet.OverlayMessage, nil +} + +// PacketType implements the sda.MessageProxy interface by returning the type of +// the ProtocolPacket. +func (p *MessageProxy) PacketType() network.PacketTypeID { + return ProtocolPacketID +} + +// Name implements the sda.MessageProxy interface by returning the name under +// which cosi.MessageProxy is registered. +func (p *MessageProxy) Name() string { + return Name +} + +// Announcement is sent down the tree to start the collective signature. +type Announcement struct { +} + +// Commitment of all nodes, aggregated over all children. +type Commitment struct { + Comm abstract.Point +} + +// Challenge is the challenge against the aggregate commitment. +type Challenge struct { + Chall abstract.Scalar +} + +// Response of all nodes, aggregated over all children. +type Response struct { + Resp abstract.Scalar +} + +// Overlay-structures to retrieve the sending TreeNode. +type chanAnnouncement struct { + *sda.TreeNode + Announcement +} + +type chanCommitment struct { + *sda.TreeNode + Commitment +} + +type chanChallenge struct { + *sda.TreeNode + Challenge +} + +type chanResponse struct { + *sda.TreeNode + Response +} diff --git a/protocols/cosimul/protocol.go b/protocols/cosimul/protocol.go index ddb96b24..7757dd00 100644 --- a/protocols/cosimul/protocol.go +++ b/protocols/cosimul/protocol.go @@ -1,8 +1,8 @@ package cosimul import ( - "github.com/dedis/cosi/protocol" "github.com/dedis/cothority/log" + p "github.com/dedis/cothority/protocols/cosi" "github.com/dedis/cothority/sda" "github.com/dedis/crypto/abstract" ) @@ -37,18 +37,18 @@ var VerifyResponse = RootCheck // CoSimul is a protocol suited for simulation type CoSimul struct { - *protocol.CoSi + *p.CoSi } // NewCoSimul returns a new CoSi-protocol suited for simulation func NewCoSimul(node *sda.TreeNodeInstance) (sda.ProtocolInstance, error) { - c, err := protocol.NewCoSi(node) + c, err := p.NewProtocol(node) if err != nil { return nil, err } - cosimul := &CoSimul{c.(*protocol.CoSi)} - cosimul.RegisterResponseHook(cosimul.getResponse, false) + cosimul := &CoSimul{c.(*p.CoSi)} + cosimul.RegisterResponseHook(cosimul.getResponse) return cosimul, nil } diff --git a/protocols/jvss/handlers.go b/protocols/jvss/handlers.go index 359bc1ba..a814e1b7 100644 --- a/protocols/jvss/handlers.go +++ b/protocols/jvss/handlers.go @@ -4,10 +4,22 @@ import ( "strings" "github.com/dedis/cothority/log" + "github.com/dedis/cothority/network" "github.com/dedis/cothority/sda" "github.com/dedis/crypto/poly" ) +func init() { + for _, i := range []interface{}{ + SecInitMsg{}, + SecConfMsg{}, + SigReqMsg{}, + SigRespMsg{}, + } { + network.RegisterPacketType(i) + } +} + // SecInitMsg are used to initialise new shared secrets both long- and // short-term. type SecInitMsg struct { diff --git a/protocols/manage/broadcast.go b/protocols/manage/broadcast.go index e1cba39d..ad70f5f1 100644 --- a/protocols/manage/broadcast.go +++ b/protocols/manage/broadcast.go @@ -4,10 +4,13 @@ import ( "errors" "github.com/dedis/cothority/log" + "github.com/dedis/cothority/network" "github.com/dedis/cothority/sda" ) func init() { + network.RegisterPacketType(ContactNodes{}) + network.RegisterPacketType(Done{}) sda.GlobalProtocolRegister("Broadcast", NewBroadcastProtocol) } @@ -60,14 +63,14 @@ func (b *Broadcast) Start() error { func (b *Broadcast) handleContactNodes(msg struct { *sda.TreeNode ContactNodes -}) { +}) error { log.Lvl3(b.Info(), "Received message from", msg.TreeNode.String()) if msg.TreeNode.ID == b.Root().ID { b.repliesLeft = len(b.Tree().List()) - b.tnIndex - 1 if b.repliesLeft == 0 { log.Lvl3("Won't contact anybody - finishing") b.SendTo(b.Root(), &Done{}) - return + return nil } log.Lvl3(b.Info(), "Contacting nodes:", b.repliesLeft) // Connect to all nodes that are later in the TreeNodeList, but only if @@ -76,7 +79,7 @@ func (b *Broadcast) handleContactNodes(msg struct { log.Lvl3("Connecting to", tn.String()) err := b.SendTo(tn, &ContactNodes{}) if err != nil { - return + return nil } } } else { @@ -84,6 +87,7 @@ func (b *Broadcast) handleContactNodes(msg struct { log.Lvl3("Sending back to", msg.TreeNode.ServerIdentity.String()) b.SendTo(msg.TreeNode, &Done{}) } + return nil } // Every node being contacted sends back a Done to the root which has @@ -91,7 +95,7 @@ func (b *Broadcast) handleContactNodes(msg struct { func (b *Broadcast) handleDone(msg struct { *sda.TreeNode Done -}) { +}) error { b.repliesLeft-- log.Lvl3(b.Info(), "Got reply and waiting for more:", b.repliesLeft) if b.repliesLeft == 0 { @@ -106,6 +110,7 @@ func (b *Broadcast) handleDone(msg struct { } } + return nil } // RegisterOnDone takes a function that will be called once all connections diff --git a/protocols/manage/close_all.go b/protocols/manage/close_all.go index 6c3e3502..a388a528 100644 --- a/protocols/manage/close_all.go +++ b/protocols/manage/close_all.go @@ -72,7 +72,7 @@ func (p *ProtocolCloseAll) Start() error { } // FuncPrepareClose sends a `PrepareClose`-message down the tree. -func (p *ProtocolCloseAll) FuncPrepareClose(pc PrepareCloseMsg) { +func (p *ProtocolCloseAll) FuncPrepareClose(pc PrepareCloseMsg) error { log.Lvl3(pc.ServerIdentity.Address, "sent PrepClose to", p.ServerIdentity().Address) if !p.IsLeaf() { for _, c := range p.Children() { @@ -82,12 +82,13 @@ func (p *ProtocolCloseAll) FuncPrepareClose(pc PrepareCloseMsg) { } else { p.FuncClose(nil) } + return nil } // FuncClose is called from the leafs to the parents and up the tree. Everybody // receiving all `Close`-messages from all children will close down all // network communication. -func (p *ProtocolCloseAll) FuncClose(c []CloseMsg) { +func (p *ProtocolCloseAll) FuncClose(c []CloseMsg) error { if !p.IsRoot() { log.Lvl3("Sending closeall from", p.ServerIdentity().Address, "to", p.Parent().ServerIdentity.Address) @@ -107,4 +108,5 @@ func (p *ProtocolCloseAll) FuncClose(c []CloseMsg) { p.Done <- true } p.TreeNodeInstance.Done() + return nil } diff --git a/protocols/manage/propagate.go b/protocols/manage/propagate.go index a74811f8..e0adcab7 100644 --- a/protocols/manage/propagate.go +++ b/protocols/manage/propagate.go @@ -13,14 +13,15 @@ import ( ) func init() { - sda.GlobalProtocolRegister("Propagate", NewPropagateProtocol) + network.RegisterPacketType(PropagateSendData{}) + network.RegisterPacketType(PropagateReply{}) } // Propagate is a protocol that sends some data to all attached nodes // and waits for confirmation before returning. type Propagate struct { *sda.TreeNodeInstance - onData func(network.Body) + onData PropagationStore onDoneCb func(int) sd *PropagateSendData ChannelSD chan struct { @@ -32,8 +33,8 @@ type Propagate struct { PropagateReply } - received int - subtree int + received int + subtreeCount int sync.Mutex } @@ -51,22 +52,55 @@ type PropagateReply struct { Level int } -// PropagateStartAndWait starts the propagation protocol and blocks until +// PropagationFunc starts the propagation protocol and blocks until // all children stored the new value or the timeout has been reached. // The return value is the number of nodes that acknowledged having // stored the new value or an error if the protocol couldn't start. -func PropagateStartAndWait(c *sda.Context, el *sda.Roster, msg network.Body, msec int, f func(network.Body)) (int, error) { - tree := el.GenerateNaryTreeWithRoot(8, c.ServerIdentity()) - log.Lvl3("Starting to propagate", reflect.TypeOf(msg)) - pi, err := c.CreateProtocolService("Propagate", tree) - if err != nil { - return -1, err - } - return propagateStartAndWait(pi, msg, msec, f) +type PropagationFunc func(el *sda.Roster, msg network.Body, msec int) (int, error) + +// PropagationStore is the function that will store the new data. +type PropagationStore func(network.Body) + +// propagationContext is used for testing. +type propagationContext interface { + ProtocolRegister(name string, protocol sda.NewProtocol) (sda.ProtocolID, error) + ServerIdentity() *network.ServerIdentity + CreateProtocolSDA(name string, t *sda.Tree) (sda.ProtocolInstance, error) +} + +// NewPropagationFunc registers a new protocol name with the context c and will +// set f as handler for every new instance of that protocol. +func NewPropagationFunc(c propagationContext, name string, f PropagationStore) (PropagationFunc, error) { + pid, err := c.ProtocolRegister(name, func(n *sda.TreeNodeInstance) (sda.ProtocolInstance, error) { + p := &Propagate{ + sd: &PropagateSendData{[]byte{}, 1000}, + TreeNodeInstance: n, + received: 0, + subtreeCount: n.TreeNode().SubtreeCount(), + onData: f, + } + for _, h := range []interface{}{&p.ChannelSD, &p.ChannelReply} { + if err := p.RegisterChannel(h); err != nil { + return nil, err + } + } + return p, nil + }) + log.Lvl3("Registering new propagation for", c.ServerIdentity(), + name, pid) + return func(el *sda.Roster, msg network.Body, msec int) (int, error) { + tree := el.GenerateNaryTreeWithRoot(8, c.ServerIdentity()) + log.Lvl3(el.List[0].Address, "Starting to propagate", reflect.TypeOf(msg)) + pi, err := c.CreateProtocolSDA(name, tree) + if err != nil { + return -1, err + } + return propagateStartAndWait(pi, msg, msec, f) + }, err } // Separate function for testing -func propagateStartAndWait(pi sda.ProtocolInstance, msg network.Body, msec int, f func(network.Body)) (int, error) { +func propagateStartAndWait(pi sda.ProtocolInstance, msg network.Body, msec int, f PropagationStore) (int, error) { d, err := network.MarshalRegisteredType(msg) if err != nil { return -1, err @@ -88,22 +122,6 @@ func propagateStartAndWait(pi sda.ProtocolInstance, msg network.Body, msec int, return ret, nil } -// NewPropagateProtocol returns a new Propagate protocol -func NewPropagateProtocol(n *sda.TreeNodeInstance) (sda.ProtocolInstance, error) { - p := &Propagate{ - sd: &PropagateSendData{[]byte{}, 1000}, - TreeNodeInstance: n, - received: 0, - subtree: n.TreeNode().SubtreeCount(), - } - for _, h := range []interface{}{&p.ChannelSD, &p.ChannelReply} { - if err := p.RegisterChannel(h); err != nil { - return nil, err - } - } - return p, nil -} - // Start will contact everyone and make the connections func (p *Propagate) Start() error { log.Lvl4("going to contact", p.Root().ServerIdentity) @@ -141,11 +159,11 @@ func (p *Propagate) Dispatch() error { } case <-p.ChannelReply: p.received++ - log.Lvl4(p.ServerIdentity(), "received:", p.received, p.subtree) + log.Lvl4(p.ServerIdentity(), "received:", p.received, p.subtreeCount) if !p.IsRoot() { p.SendToParent(&PropagateReply{}) } - if p.received == p.subtree { + if p.received == p.subtreeCount { process = false } case <-time.After(timeout): @@ -163,17 +181,16 @@ func (p *Propagate) Dispatch() error { return nil } -// RegisterOnDone takes a function that will be called once all connections -// are set up. The argument to the function is the number of children that -// sent OK after the propagation +// RegisterOnDone takes a function that will be called once the data has been +// sent to the whole tree. It receives the number of nodes that replied +// successfully to the propagation. func (p *Propagate) RegisterOnDone(fn func(int)) { p.onDoneCb = fn } -// RegisterOnData takes a function that will be called once all connections -// are set up. The argument to the function is the number of children that -// sent OK after the propagation -func (p *Propagate) RegisterOnData(fn func(network.Body)) { +// RegisterOnData takes a function that will be called for that node if it +// needs to update its data. +func (p *Propagate) RegisterOnData(fn PropagationStore) { p.onData = fn } diff --git a/protocols/manage/propagate_test.go b/protocols/manage/propagate_test.go index c58939cc..8554fb25 100644 --- a/protocols/manage/propagate_test.go +++ b/protocols/manage/propagate_test.go @@ -1,6 +1,7 @@ package manage import ( + "sync" "testing" "bytes" @@ -22,34 +23,56 @@ func init() { // Tests an n-node system func TestPropagate(t *testing.T) { - for _, nbrNodes := range []int{3 /*10, 14*/} { + for _, nbrNodes := range []int{3, 10, 14} { local := sda.NewLocalTest() - _, el, _ := local.GenTree(nbrNodes, true) - o := local.Overlays[el.List[0].ID] - - i := 0 + conodes, el, _ := local.GenTree(nbrNodes, true) + var i int + var iMut sync.Mutex msg := &PropagateMsg{[]byte("propagate")} - - tree := el.GenerateNaryTreeWithRoot(8, o.ServerIdentity()) + propFuncs := make([]PropagationFunc, nbrNodes) + var err error + for n, conode := range conodes { + pc := &PC{conode, local.Overlays[conode.ServerIdentity.ID]} + propFuncs[n], err = NewPropagationFunc(pc, + "Propagate", + func(m network.Body) { + if bytes.Equal(msg.Data, m.(*PropagateMsg).Data) { + iMut.Lock() + i++ + iMut.Unlock() + } else { + t.Error("Didn't receive correct data") + } + }) + log.ErrFatal(err) + } log.Lvl2("Starting to propagate", reflect.TypeOf(msg)) - pi, err := o.CreateProtocolSDA("Propagate", tree) + children, err := propFuncs[0](el, msg, 1000) log.ErrFatal(err) - nodes, err := propagateStartAndWait(pi, msg, 1000, - func(m network.Body) { - if bytes.Equal(msg.Data, m.(*PropagateMsg).Data) { - i++ - } else { - t.Error("Didn't receive correct data") - } - }) - log.ErrFatal(err) - if i != 1 { + + if i != nbrNodes { t.Fatal("Didn't get data-request") } - if nodes != nbrNodes { + if children != nbrNodes { t.Fatal("Not all nodes replied") } local.CloseAll() log.AfterTest(t) } } + +type PC struct { + C *sda.Conode + O *sda.Overlay +} + +func (pc *PC) ProtocolRegister(name string, protocol sda.NewProtocol) (sda.ProtocolID, error) { + return pc.C.ProtocolRegister(name, protocol) +} +func (pc *PC) ServerIdentity() *network.ServerIdentity { + return pc.C.ServerIdentity + +} +func (pc *PC) CreateProtocolSDA(name string, t *sda.Tree) (sda.ProtocolInstance, error) { + return pc.O.CreateProtocolSDA(name, t) +} diff --git a/protocols/ntree/ntree.go b/protocols/ntree/ntree.go index 72e96d6f..8328d3db 100644 --- a/protocols/ntree/ntree.go +++ b/protocols/ntree/ntree.go @@ -88,7 +88,7 @@ func (p *Protocol) HandleSignRequest(msg structMessage) error { // HandleSignBundle is a handler responsible for adding the node's signature // and verifying the children's signatures (verification level can be controlled // by the VerifySignature flag). -func (p *Protocol) HandleSignBundle(reply []structSignatureBundle) { +func (p *Protocol) HandleSignBundle(reply []structSignatureBundle) error { log.Lvl3("Appending our signature to the collected ones and send to parent") var sig SignatureBundle sig.ChildSig = p.signature @@ -128,6 +128,7 @@ func (p *Protocol) HandleSignBundle(reply []structSignatureBundle) { log.Lvl3("Leader got", len(reply), "signatures. Children:", len(p.Children())) p.Done() } + return nil } func (p *Protocol) verifySignatureReply(sig *SignatureReply) string { diff --git a/sda/context.go b/sda/context.go index 7b5384d3..343a9c2a 100644 --- a/sda/context.go +++ b/sda/context.go @@ -26,7 +26,8 @@ func newContext(c *Conode, o *Overlay, servID ServiceID, manager *serviceManager // NewTreeNodeInstance creates a TreeNodeInstance that is bound to a // service instead of the Overlay. func (c *Context) NewTreeNodeInstance(t *Tree, tn *TreeNode, protoName string) *TreeNodeInstance { - return c.overlay.NewTreeNodeInstanceFromService(t, tn, ProtocolNameToID(protoName), c.servID) + io := c.overlay.protoIO.getByName(protoName) + return c.overlay.NewTreeNodeInstanceFromService(t, tn, ProtocolNameToID(protoName), c.servID, io) } // SendRaw sends a message to the ServerIdentity. @@ -57,6 +58,15 @@ func (c *Context) CreateProtocolSDA(name string, t *Tree) (ProtocolInstance, err return pi, err } +// ProtocolRegister signs up a new protocol to this Conode. Contrary go +// GlobalProtocolRegister, the protocol registered here is tied to that conode. +// This is useful for simulations where more than one Conode exists in the +// global namespace. +// It returns the ID of the protocol. +func (c *Context) ProtocolRegister(name string, protocol NewProtocol) (ProtocolID, error) { + return c.conode.ProtocolRegister(name, protocol) +} + // RegisterProtocolInstance registers a new instance of a protocol using overlay. func (c *Context) RegisterProtocolInstance(pi ProtocolInstance) error { return c.overlay.RegisterProtocolInstance(pi) @@ -78,6 +88,12 @@ func (c *Context) RegisterProcessor(p network.Processor, msgType network.PacketT c.manager.RegisterProcessor(p, msgType) } +// RegisterProcessorFunc takes a message-type and a function that will be called +// if this message-type is received. +func (c *Context) RegisterProcessorFunc(msgType network.PacketTypeID, fn func(*network.Packet)) { + c.manager.RegisterProcessorFunc(msgType, fn) +} + // Service returns the corresponding service. func (c *Context) Service(name string) Service { return c.manager.Service(name) diff --git a/sda/export_test.go b/sda/export_test.go index a4c1c80a..f66c37da 100644 --- a/sda/export_test.go +++ b/sda/export_test.go @@ -1,13 +1,5 @@ package sda -import "github.com/dedis/cothority/network" - -// Export some private functions of Host for testing - -func (c *Conode) SendSDAData(id *network.ServerIdentity, msg *ProtocolMsg) error { - return c.overlay.sendSDAData(id, msg) -} - func (c *Conode) CreateProtocol(name string, t *Tree) (ProtocolInstance, error) { return c.overlay.CreateProtocolSDA(name, t) } @@ -26,10 +18,6 @@ func (c *Conode) GetTree(id TreeID) (*Tree, bool) { return t, t != nil } -func (c *Conode) SendToTreeNode(from *Token, to *TreeNode, msg network.Body) error { - return c.overlay.SendToTreeNode(from, to, msg) -} - func (c *Conode) Overlay() *Overlay { return c.overlay } diff --git a/sda/local.go b/sda/local.go index b32279dd..68a7f911 100644 --- a/sda/local.go +++ b/sda/local.go @@ -208,7 +208,8 @@ func (l *LocalTest) NewTreeNodeInstance(tn *TreeNode, protName string) (*TreeNod TreeNodeID: tn.ID, RoundID: RoundID(uuid.NewV4()), } - node := newTreeNodeInstance(o, tok, tn) + io := o.protoIO.getByName(protName) + node := newTreeNodeInstance(o, tok, tn, io) l.Nodes = append(l.Nodes, node) return node, nil } @@ -228,17 +229,14 @@ func (l *LocalTest) SendTreeNode(proto string, from, to *TreeNodeInstance, msg n if from.Tree().ID != to.Tree().ID { return errors.New("Can't send from one tree to another") } - b, err := network.MarshalRegisteredType(msg) - if err != nil { - return err - } sdaMsg := &ProtocolMsg{ - MsgSlice: b, - MsgType: network.TypeToPacketTypeID(msg), - From: from.token, - To: to.token, + Msg: msg, + MsgType: network.TypeToPacketTypeID(msg), + From: from.token, + To: to.token, } - return to.overlay.TransmitMsg(sdaMsg) + io := l.Overlays[to.ServerIdentity().ID].protoIO.getByName(proto) + return to.overlay.TransmitMsg(sdaMsg, io) } // AddPendingTreeMarshal takes a treeMarshal and adds it to the list of the diff --git a/sda/messages.go b/sda/messages.go index 2788421b..09049a7d 100644 --- a/sda/messages.go +++ b/sda/messages.go @@ -45,8 +45,7 @@ type ProtocolMsg struct { // RoundID uniquely identifies a round of a protocol run type RoundID uuid.UUID -// String returns the canonical representation of the rounds ID (wrapper around -// uuid.UUID.String()) +// String returns the canonical representation of the rounds ID (wrapper around // uuid.UUID.String()) func (rId RoundID) String() string { return uuid.UUID(rId).String() } @@ -110,6 +109,23 @@ func (t *Token) ChangeTreeNodeID(newid TreeNodeID) *Token { return &tOther } +// TreeNodeInfo holds the sender and the destination of the message. +type TreeNodeInfo struct { + To *Token + From *Token +} + +// OverlayMessage contains all routing-information about the tree and the +// roster. +type OverlayMessage struct { + TreeNodeInfo *TreeNodeInfo + + RequestTree *RequestTree + TreeMarshal *TreeMarshal + RequestRoster *RequestRoster + Roster *Roster +} + // RequestTree is used to ask the parent for a given Tree type RequestTree struct { // The treeID of the tree we want diff --git a/sda/node_test.go b/sda/node_test.go index c490943e..81bfaee9 100644 --- a/sda/node_test.go +++ b/sda/node_test.go @@ -316,8 +316,10 @@ func NewProtocolHandlers(n *TreeNodeInstance) (ProtocolInstance, error) { p := &ProtocolHandlers{ TreeNodeInstance: n, } - p.RegisterHandler(p.HandleMessageOne) - p.RegisterHandler(p.HandleMessageAggregate) + if err := p.RegisterHandlers(p.HandleMessageOne, + p.HandleMessageAggregate); err != nil { + return nil, err + } return p, nil } @@ -334,16 +336,18 @@ func (p *ProtocolHandlers) Start() error { func (p *ProtocolHandlers) HandleMessageOne(msg struct { *TreeNode NodeTestMsg -}) { +}) error { IncomingHandlers <- p.TreeNodeInstance + return nil } func (p *ProtocolHandlers) HandleMessageAggregate(msg []struct { *TreeNode NodeTestAggMsg -}) { +}) error { log.Lvl3("Received message") IncomingHandlers <- p.TreeNodeInstance + return nil } func (p *ProtocolHandlers) Dispatch() error { @@ -422,7 +426,7 @@ func NewProtocolBlocking(node *TreeNodeInstance) (ProtocolInstance, error) { stopBlockChan: make(chan bool), } - node.RegisterChannel(&bp.Incoming) + log.ErrFatal(node.RegisterChannel(&bp.Incoming)) return bp, nil } diff --git a/sda/overlay.go b/sda/overlay.go index e7320222..a9bafcc4 100644 --- a/sda/overlay.go +++ b/sda/overlay.go @@ -40,11 +40,22 @@ type Overlay struct { // pendingSDAData are a list of message we received that does not correspond // to any local Tree or/and Roster. We first request theses so we can // instantiate properly protocolInstance that will use these SDAData msg. - pendingSDAs []*ProtocolMsg + pendingMsg []pendingMsg // lock associated with pending SDAdata - pendingSDAsLock sync.Mutex + pendingMsgLock sync.Mutex transmitMux sync.Mutex + + protoIO *messageProxyStore +} + +// pendingMsg is used to store messages destined for ProtocolInstances but when +// the tree designated is not known to the Overlay. When the tree is sent to the +// overlay, then the pendingMsg that are relying on this tree will get +// processed. +type pendingMsg struct { + *ProtocolMsg + MessageProxy } // NewOverlay creates a new overlay-structure @@ -58,8 +69,8 @@ func NewOverlay(c *Conode) *Overlay { instancesInfo: make(map[TokenID]bool), protocolInstances: make(map[TokenID]ProtocolInstance), pendingTreeMarshal: make(map[RosterID][]*TreeMarshal), - pendingSDAs: make([]*ProtocolMsg, 0), } + o.protoIO = newMessageProxyStore(c, o) // messages going to protocol instances c.RegisterProcessor(o, SDADataMessageID, // protocol instance's messages @@ -73,87 +84,32 @@ func NewOverlay(c *Conode) *Overlay { // Process implements the Processor interface so it process the messages that it // wants. func (o *Overlay) Process(data *network.Packet) { - switch data.MsgType { - case SDADataMessageID: - sdaMsg := data.Msg.(ProtocolMsg) - sdaMsg.ServerIdentity = data.ServerIdentity - err := o.TransmitMsg(&sdaMsg) - if err != nil { - log.Error("ProcessSDAMessage returned:", err) - } - - case RequestTreeMessageID: - // A host has sent us a request to get a tree definition - tid := data.Msg.(RequestTree).TreeID - tree := o.Tree(tid) - var err error - if tree != nil { - err = o.conode.Send(data.ServerIdentity, tree.MakeTreeMarshal()) - } else { - // XXX Take care here for we must verify at the other side that - // the tree is Nil. Should we think of a way of sending back an - // "error" ? - err = o.conode.Send(data.ServerIdentity, (&Tree{}).MakeTreeMarshal()) - } - if err != nil { - log.Error("Couldn't send tree:", err) - } - case SendTreeMessageID: - // A Host has replied to our request of a tree - tm := data.Msg.(TreeMarshal) - if tm.TreeID == TreeID(uuid.Nil) { - log.Error("Received an empty Tree") - return - } - il := o.Roster(tm.RosterID) - // The entity list does not exists, we should request that, too - if il == nil { - msg := &RequestRoster{tm.RosterID} - if err := o.conode.Send(data.ServerIdentity, msg); err != nil { - log.Error("Requesting Roster in SendTree failed", err) - } - - // put the tree marshal into pending queue so when we receive the - // entitylist we can create the real Tree. - o.addPendingTreeMarshal(&tm) - return - } - - tree, err := tm.MakeTree(il) - if err != nil { - log.Error("Couldn't create tree:", err) - return - } - log.Lvl4("Received new tree") - o.RegisterTree(tree) - case RequestRosterMessageID: - // Some host requested an Roster - id := data.Msg.(RequestRoster).RosterID - el := o.Roster(id) - var err error - if el != nil { - err = o.conode.Send(data.ServerIdentity, el) - } else { - log.Lvl2("Requested entityList that we don't have") - err = o.conode.Send(data.ServerIdentity, &Roster{}) - } - if err != nil { - log.Error("Couldn't send empty entity list from host:", - o.conode.ServerIdentity.String(), - err) - return - } - case SendRosterMessageID: - // Host replied to our request of entitylist - il := data.Msg.(Roster) - if il.ID == RosterID(uuid.Nil) { - log.Lvl2("Received an empty Roster") - } else { - o.RegisterRoster(&il) - // Check if some trees can be constructed from this entitylist - o.checkPendingTreeMarshal(&il) + // get messageProxy or default one + io := o.protoIO.getByPacketType(data.MsgType) + inner, info, err := io.Unwrap(data.Msg) + if err != nil { + log.Error("unwrapping: ", err) + return + } + switch true { + case info.RequestTree != nil: + o.handleRequestTree(data.ServerIdentity, info.RequestTree, io) + case info.TreeMarshal != nil: + o.handleSendTree(data.ServerIdentity, info.TreeMarshal, io) + case info.RequestRoster != nil: + o.handleRequestRoster(data.ServerIdentity, info.RequestRoster, io) + case info.Roster != nil: + o.handleSendRoster(data.ServerIdentity, info.Roster) + default: + typ := network.TypeToPacketTypeID(inner) + sda := &ProtocolMsg{ + From: info.TreeNodeInfo.From, + To: info.TreeNodeInfo.To, + ServerIdentity: data.ServerIdentity, + Msg: inner, + MsgType: typ, } - log.Lvl4("Received new entityList") + o.TransmitMsg(sda, io) } } @@ -162,11 +118,12 @@ func (o *Overlay) Process(data *network.Packet) { // - ask for the Tree // - create a new protocolInstance // - pass it to a given protocolInstance -func (o *Overlay) TransmitMsg(sdaMsg *ProtocolMsg) error { - +// io is the messageProxy to use if a specific wireformat protocol is used. +// It can be nil: in that case it fall backs to default wire protocol. +func (o *Overlay) TransmitMsg(sdaMsg *ProtocolMsg, io MessageProxy) error { tree := o.Tree(sdaMsg.To.TreeID) if tree == nil { - return o.requestTree(sdaMsg.ServerIdentity, sdaMsg) + return o.requestTree(sdaMsg.ServerIdentity, sdaMsg, io) } o.transmitMux.Lock() defer o.transmitMux.Unlock() @@ -187,7 +144,7 @@ func (o *Overlay) TransmitMsg(sdaMsg *ProtocolMsg) error { if err != nil { return errors.New("No TreeNode defined in this tree here") } - tni := o.newTreeNodeInstanceFromToken(tn, sdaMsg.To) + tni := o.newTreeNodeInstanceFromToken(tn, sdaMsg.To, io) // see if we know the Service Recipient s, ok := o.conode.serviceManager.serviceByID(sdaMsg.To.ServiceID) // no servies defined => check if there is a protocol that can be @@ -227,23 +184,6 @@ func (o *Overlay) TransmitMsg(sdaMsg *ProtocolMsg) error { return nil } -// sendSDAData marshals the inner msg and then sends a Data msg -// to the appropriate entity -func (o *Overlay) sendSDAData(si *network.ServerIdentity, sdaMsg *ProtocolMsg) error { - b, err := network.MarshalRegisteredType(sdaMsg.Msg) - if err != nil { - log.Error(err) - return fmt.Errorf("Error marshaling message: %s (msg = %+v)", err.Error(), sdaMsg.Msg) - } - sdaMsg.MsgSlice = b - sdaMsg.MsgType = network.TypeFromData(sdaMsg.Msg) - // put to nil so protobuf won't encode it and there won't be any error on the - // other side (because it doesn't know how to decode it) - sdaMsg.Msg = nil - log.Lvl4(o.conode.Address(), "Sending to", si.Address) - return o.conode.Send(si, sdaMsg) -} - // addPendingTreeMarshal adds a treeMarshal to the list. // This list is checked each time we receive a new Roster // so trees using this Roster can be constructed. @@ -262,28 +202,25 @@ func (o *Overlay) addPendingTreeMarshal(tm *TreeMarshal) { // checkPendingMessages is called each time we receive a new tree if there are some SDA // messages using this tree. If there are, we can make an instance of a protocolinstance -// and give it the message!. -// NOTE: put that as a go routine so the rest of the processing messages are not -// slowed down, if there are many pending sda message at once (i.e. start many new -// protocols at same time) +// and give it the message. func (o *Overlay) checkPendingMessages(t *Tree) { go func() { - o.pendingSDAsLock.Lock() - var newPending []*ProtocolMsg - for _, msg := range o.pendingSDAs { - if t.ID.Equals(msg.To.TreeID) { + o.pendingMsgLock.Lock() + var newPending []pendingMsg + for _, pending := range o.pendingMsg { + if t.ID.Equals(pending.ProtocolMsg.To.TreeID) { // if this message references t, instantiate it and go - err := o.TransmitMsg(msg) + err := o.TransmitMsg(pending.ProtocolMsg, pending.MessageProxy) if err != nil { log.Error(o.conode.ServerIdentity.Address, "TransmitMsg failed:", err) continue } } else { - newPending = append(newPending, msg) + newPending = append(newPending, pending) } } - o.pendingSDAs = newPending - o.pendingSDAsLock.Unlock() + o.pendingMsg = newPending + o.pendingMsgLock.Unlock() }() } @@ -309,17 +246,34 @@ func (o *Overlay) checkPendingTreeMarshal(el *Roster) { o.pendingTreeLock.Unlock() } +func (o *Overlay) savePendingMsg(sdaMsg *ProtocolMsg, io MessageProxy) { + o.pendingMsgLock.Lock() + o.pendingMsg = append(o.pendingMsg, pendingMsg{ + ProtocolMsg: sdaMsg, + MessageProxy: io, + }) + o.pendingMsgLock.Unlock() + +} + // requestTree will ask for the tree the sdadata is related to. // it will put the message inside the pending list of sda message waiting to // have their trees. -func (o *Overlay) requestTree(si *network.ServerIdentity, sdaMsg *ProtocolMsg) error { - o.pendingSDAsLock.Lock() - o.pendingSDAs = append(o.pendingSDAs, sdaMsg) - o.pendingSDAsLock.Unlock() +// io is the wrapper to use to send the message, it can be nil. +func (o *Overlay) requestTree(si *network.ServerIdentity, sdaMsg *ProtocolMsg, io MessageProxy) error { + o.savePendingMsg(sdaMsg, io) - treeRequest := &RequestTree{sdaMsg.To.TreeID} + var msg interface{} + om := &OverlayMessage{ + RequestTree: &RequestTree{sdaMsg.To.TreeID}, + } + msg, err := io.Wrap(nil, om) + if err != nil { + return err + } - return o.conode.Send(si, treeRequest) + err = o.conode.Send(si, msg) + return err } // RegisterTree takes a tree and puts it in the map @@ -366,7 +320,7 @@ func (o *Overlay) Roster(elid RosterID) *Roster { // TreeNodeFromToken returns the treeNode corresponding to a token func (o *Overlay) TreeNodeFromToken(t *Token) (*TreeNode, error) { if t == nil { - return nil, errors.New("Didn't find tree-node: No token given.") + return nil, errors.New("didn't find tree-node: No token given") } // First, check the cache if tn := o.cache.GetFromToken(t); tn != nil { @@ -375,26 +329,132 @@ func (o *Overlay) TreeNodeFromToken(t *Token) (*TreeNode, error) { // If cache has not, then search the tree tree := o.Tree(t.TreeID) if tree == nil { - return nil, errors.New("Didn't find tree") + return nil, errors.New("didn't find tree") } tn := tree.Search(t.TreeNodeID) if tn == nil { - return nil, errors.New("Didn't find treenode") + return nil, errors.New("didn't find treenode") } // Since we found treeNode, cache it so later reuse o.cache.Cache(tree, tn) return tn, nil } +func (o *Overlay) handleRequestTree(si *network.ServerIdentity, req *RequestTree, io MessageProxy) { + tid := req.TreeID + tree := o.Tree(tid) + var err error + var treeM *TreeMarshal + var msg interface{} + if tree != nil { + treeM = tree.MakeTreeMarshal() + } else { + // XXX Take care here for we must verify at the other side that + // the tree is Nil. Should we think of a way of sending back an + // "error" ? + treeM = (&Tree{}).MakeTreeMarshal() + } + msg, err = io.Wrap(nil, &OverlayMessage{ + TreeMarshal: treeM, + }) + + if err != nil { + log.Error("couldn't wrap TreeMarshal:", err) + return + } + + err = o.conode.Send(si, msg) + if err != nil { + log.Error("Couldn't send tree:", err) + } +} + +func (o *Overlay) handleSendTree(si *network.ServerIdentity, tm *TreeMarshal, io MessageProxy) { + if tm.TreeID == TreeID(uuid.Nil) { + log.Error("Received an empty Tree") + return + } + roster := o.Roster(tm.RosterID) + // The roster does not exists, we should request that, too + if roster == nil { + msg, err := io.Wrap(nil, &OverlayMessage{ + RequestRoster: &RequestRoster{tm.RosterID}, + }) + if err != nil { + log.Error("could not wrap RequestRoster:", err) + } + if err := o.conode.Send(si, msg); err != nil { + log.Error("Requesting Roster in SendTree failed", err) + } + // put the tree marshal into pending queue so when we receive the + // entitylist we can create the real Tree. + o.addPendingTreeMarshal(tm) + return + } + + tree, err := tm.MakeTree(roster) + if err != nil { + log.Error("Couldn't create tree:", err) + return + } + log.Lvl4("Received new tree") + o.RegisterTree(tree) +} + +func (o *Overlay) handleRequestRoster(si *network.ServerIdentity, req *RequestRoster, io MessageProxy) { + id := req.RosterID + roster := o.Roster(id) + var err error + var msg interface{} + if roster == nil { + // XXX Bad reaction to request... + log.Lvl2("Requested entityList that we don't have") + roster = &Roster{} + } + + msg, err = io.Wrap(nil, &OverlayMessage{ + Roster: roster, + }) + + if err != nil { + log.Error("error wraping up roster:", err) + return + } + + err = o.conode.Send(si, msg) + if err != nil { + log.Error("Couldn't send empty entity list from host:", + o.conode.ServerIdentity.String(), + err) + return + } +} + +func (o *Overlay) handleSendRoster(si *network.ServerIdentity, roster *Roster) { + if roster.ID == RosterID(uuid.Nil) { + log.Lvl2("Received an empty Roster") + } else { + o.RegisterRoster(roster) + // Check if some trees can be constructed from this entitylist + o.checkPendingTreeMarshal(roster) + } + log.Lvl4("Received new entityList") +} + // SendToTreeNode sends a message to a treeNode -func (o *Overlay) SendToTreeNode(from *Token, to *TreeNode, msg network.Body) error { - sda := &ProtocolMsg{ - Msg: msg, - From: from, - To: from.ChangeTreeNodeID(to.ID), +func (o *Overlay) SendToTreeNode(from *Token, to *TreeNode, msg network.Body, io MessageProxy) error { + var final interface{} + info := &OverlayMessage{ + TreeNodeInfo: &TreeNodeInfo{ + From: from, + To: from.ChangeTreeNodeID(to.ID), + }, + } + final, err := io.Wrap(msg, info) + if err != nil { + return err } - log.Lvl4(o.conode.Address(), "Sending to entity", to.ServerIdentity.Address) - return o.sendSDAData(to.ServerIdentity, sda) + return o.conode.Send(to.ServerIdentity, final) } // nodeDone is called by node to signify that its work is finished and its @@ -447,7 +507,8 @@ func (o *Overlay) CreateProtocolSDA(name string, t *Tree) (ProtocolInstance, err // CreateProtocolService adds the service-id to the token so the protocol will // be picked up by the correct service and handled by its NewProtocol method. func (o *Overlay) CreateProtocolService(name string, t *Tree, sid ServiceID) (ProtocolInstance, error) { - tni := o.NewTreeNodeInstanceFromService(t, t.Root, ProtocolNameToID(name), sid) + io := o.protoIO.getByName(name) + tni := o.NewTreeNodeInstanceFromService(t, t.Root, ProtocolNameToID(name), sid, io) pi, err := o.conode.ProtocolInstantiate(tni.token.ProtoID, tni) if err != nil { return nil, err @@ -477,12 +538,13 @@ func (o *Overlay) StartProtocol(t *Tree, name string) (ProtocolInstance, error) // NewTreeNodeInstanceFromProtoName takes a protocol name and a tree and // instantiate a TreeNodeInstance for this protocol. func (o *Overlay) NewTreeNodeInstanceFromProtoName(t *Tree, name string) *TreeNodeInstance { - return o.NewTreeNodeInstanceFromProtocol(t, t.Root, ProtocolNameToID(name)) + io := o.protoIO.getByName(name) + return o.NewTreeNodeInstanceFromProtocol(t, t.Root, ProtocolNameToID(name), io) } // NewTreeNodeInstanceFromProtocol takes a tree and a treenode (normally the // root) and and protocolID and returns a fresh TreeNodeInstance. -func (o *Overlay) NewTreeNodeInstanceFromProtocol(t *Tree, tn *TreeNode, protoID ProtocolID) *TreeNodeInstance { +func (o *Overlay) NewTreeNodeInstanceFromProtocol(t *Tree, tn *TreeNode, protoID ProtocolID, io MessageProxy) *TreeNodeInstance { tok := &Token{ TreeNodeID: tn.ID, TreeID: t.ID, @@ -490,7 +552,7 @@ func (o *Overlay) NewTreeNodeInstanceFromProtocol(t *Tree, tn *TreeNode, protoID ProtoID: protoID, RoundID: RoundID(uuid.NewV4()), } - tni := o.newTreeNodeInstanceFromToken(tn, tok) + tni := o.newTreeNodeInstanceFromToken(tn, tok, io) o.RegisterTree(t) o.RegisterRoster(t.Roster) return tni @@ -498,7 +560,7 @@ func (o *Overlay) NewTreeNodeInstanceFromProtocol(t *Tree, tn *TreeNode, protoID // NewTreeNodeInstanceFromService takes a tree, a TreeNode and a service ID and // returns a TNI. -func (o *Overlay) NewTreeNodeInstanceFromService(t *Tree, tn *TreeNode, protoID ProtocolID, servID ServiceID) *TreeNodeInstance { +func (o *Overlay) NewTreeNodeInstanceFromService(t *Tree, tn *TreeNode, protoID ProtocolID, servID ServiceID, io MessageProxy) *TreeNodeInstance { tok := &Token{ TreeNodeID: tn.ID, TreeID: t.ID, @@ -507,7 +569,7 @@ func (o *Overlay) NewTreeNodeInstanceFromService(t *Tree, tn *TreeNode, protoID ServiceID: servID, RoundID: RoundID(uuid.NewV4()), } - tni := o.newTreeNodeInstanceFromToken(tn, tok) + tni := o.newTreeNodeInstanceFromToken(tn, tok, io) o.RegisterTree(t) o.RegisterRoster(t.Roster) return tni @@ -521,8 +583,8 @@ func (o *Overlay) ServerIdentity() *network.ServerIdentity { // newTreeNodeInstanceFromToken is to be called by the Overlay when it receives // a message it does not have a treenodeinstance registered yet. The protocol is // already running so we should *not* generate a new RoundID. -func (o *Overlay) newTreeNodeInstanceFromToken(tn *TreeNode, tok *Token) *TreeNodeInstance { - tni := newTreeNodeInstance(o, tok, tn) +func (o *Overlay) newTreeNodeInstanceFromToken(tn *TreeNode, tok *Token, io MessageProxy) *TreeNodeInstance { + tni := newTreeNodeInstance(o, tok, tn, io) o.instancesLock.Lock() defer o.instancesLock.Unlock() o.instances[tok.ID()] = tni @@ -534,7 +596,7 @@ var ErrWrongTreeNodeInstance = errors.New("This TreeNodeInstance doesn't exist") // ErrProtocolRegistered is when the protocolinstance is already registered to // the overlay -var ErrProtocolRegistered = errors.New("A ProtocolInstance already has been registered using this TreeNodeInstance!") +var ErrProtocolRegistered = errors.New("a ProtocolInstance already has been registered using this TreeNodeInstance") // RegisterProtocolInstance takes a PI and stores it for dispatching the message // to it. @@ -621,3 +683,83 @@ func (tnc *TreeNodeCache) GetFromToken(tok *Token) *TreeNode { } return tn } + +// defaultProtoIO implements the ProtocoIO interface but using the "regular/old" +// wire format protocol,i.e. it wraps a message into a ProtocolMessage +type defaultProtoIO struct{} + +// Wrap implements the MessageProxy interface for the Overlay. +func (d *defaultProtoIO) Wrap(msg interface{}, info *OverlayMessage) (interface{}, error) { + if msg != nil { + buff, err := network.MarshalRegisteredType(msg) + if err != nil { + return nil, err + } + typ := network.TypeFromData(msg) + protoMsg := &ProtocolMsg{ + From: info.TreeNodeInfo.From, + To: info.TreeNodeInfo.To, + MsgSlice: buff, + MsgType: typ, + } + return protoMsg, nil + } + var returnMsg interface{} + switch true { + case info.RequestTree != nil: + returnMsg = info.RequestTree + case info.RequestRoster != nil: + returnMsg = info.RequestRoster + case info.TreeMarshal != nil: + returnMsg = info.TreeMarshal + case info.Roster != nil: + returnMsg = info.Roster + default: + panic("overlay: default wrapper has nothing to wrap") + } + return returnMsg, nil +} + +// Unwrap implements the MessageProxy interface for the Overlay. +func (d *defaultProtoIO) Unwrap(msg interface{}) (interface{}, *OverlayMessage, error) { + var returnMsg interface{} + var returnOverlay = new(OverlayMessage) + var err error + + switch inner := msg.(type) { + case ProtocolMsg: + sdaMsg := inner + var err error + _, protoMsg, err := network.UnmarshalRegistered(sdaMsg.MsgSlice) + if err != nil { + return nil, nil, err + } + // Put the msg into SDAData + returnOverlay.TreeNodeInfo = &TreeNodeInfo{ + To: sdaMsg.To, + From: sdaMsg.From, + } + returnMsg = protoMsg + case RequestTree: + returnOverlay.RequestTree = &inner + case RequestRoster: + returnOverlay.RequestRoster = &inner + case TreeMarshal: + returnOverlay.TreeMarshal = &inner + case Roster: + returnOverlay.Roster = &inner + default: + err = errors.New("default protoIO: unwraping an unknown message type") + } + return returnMsg, returnOverlay, err +} + +// Unwrap implements the MessageProxy interface for the Overlay. +func (d *defaultProtoIO) PacketType() network.PacketTypeID { + return network.PacketTypeID([16]byte{}) +} + +// Name implements the MessageProxy interface. It returns the value "default". +func (d *defaultProtoIO) Name() string { + return "default" +} diff --git a/sda/processor.go b/sda/processor.go index 760e63d7..9693c580 100644 --- a/sda/processor.go +++ b/sda/processor.go @@ -4,8 +4,6 @@ import ( "errors" "reflect" - "strings" - "github.com/dedis/cothority/log" "github.com/dedis/cothority/network" ) @@ -95,10 +93,9 @@ func (p *ServiceProcessor) RegisterMessages(procs ...interface{}) error { return nil } -// Process implements the Processor interface and dispatches ClientRequest message -// and InterServiceMessage. +// Process implements the Processor interface and dispatches ClientRequest messages. func (p *ServiceProcessor) Process(packet *network.Packet) { - p.GetReply(packet.ServerIdentity, packet.MsgType, packet.Msg) + log.Panic("Cannot handle message.") } // ProcessClientRequest takes a request from a client, calculates the reply @@ -118,36 +115,6 @@ func (p *ServiceProcessor) ProcessClientRequest(si *network.ServerIdentity, } } -// SendISM takes the message and sends it to the corresponding service. -func (p *ServiceProcessor) SendISM(si *network.ServerIdentity, msg network.Body) error { - sName := ServiceFactory.Name(p.Context.ServiceID()) - sm, err := CreateServiceMessage(sName, msg) - if err != nil { - return err - } - log.Lvl4("Raw-sending to", si) - return p.SendRaw(si, sm) -} - -// SendISMOthers sends an InterServiceMessage to all other services. -func (p *ServiceProcessor) SendISMOthers(el *Roster, msg network.Body) error { - var errStrs []string - for _, e := range el.List { - if !e.ID.Equal(p.Context.ServerIdentity().ID) { - log.Lvl3("Sending to", e) - err := p.SendISM(e, msg) - if err != nil { - errStrs = append(errStrs, err.Error()) - } - } - } - var err error - if len(errStrs) > 0 { - err = errors.New(strings.Join(errStrs, "\n")) - } - return err -} - // GetReply takes msgType and a message. It dispatches the msg to the right // function registered, then sends the responses to the sender. func (p *ServiceProcessor) GetReply(si *network.ServerIdentity, mt network.PacketTypeID, m network.Body) network.Body { diff --git a/sda/processor_test.go b/sda/processor_test.go index 2cdc9374..7f93e580 100644 --- a/sda/processor_test.go +++ b/sda/processor_test.go @@ -116,12 +116,6 @@ func TestProcessor_ProcessClientRequest(t *testing.T) { } -func mkClientRequest(msg network.Body) []byte { - b, err := network.MarshalRegisteredType(msg) - log.ErrFatal(err) - return b -} - type testMsg struct { I int } @@ -187,7 +181,3 @@ func (ts *testService) ProcessMsg(si *network.ServerIdentity, msg *testMsg) (net ts.Msg = msg return msg, nil } - -func returnMsg(si *network.ServerIdentity, msg network.Body) (network.Body, error) { - return msg, nil -} diff --git a/sda/protocol.go b/sda/protocol.go index 237ab3d7..799ea765 100644 --- a/sda/protocol.go +++ b/sda/protocol.go @@ -2,6 +2,7 @@ package sda import ( "fmt" + "sync" "github.com/dedis/cothority/log" "github.com/dedis/cothority/network" @@ -100,3 +101,95 @@ func ProtocolNameToID(name string) ProtocolID { func GlobalProtocolRegister(name string, protocol NewProtocol) (ProtocolID, error) { return protocols.Register(name, protocol) } + +// MessageProxy is an interface that allows one protocol to completely define its +// wire protocol format while still using the Overlay. +// Cothority sends different messages dynamically as slices of bytes, whereas +// Google proposes to use union-types: +// https://developers.google.com/protocol-buffers/docs/techniques#union +// This is a wrapper to enable union-types while still keeping compatibility with +// the dynamic cothority-messages. Implementations must provide methods to +// pass from the 'union-types' to 'cothority-dynamic-messages' with the Wrap +// and Unwrap method. +// A default one is provided with defaultMessageProxy so the regular wire-format +// protocol can still be used. +type MessageProxy interface { + // Wrap takes a message and the overlay information and returns the message + // that has to be sent directly to the network alongside with any error that + // happened. + // If msg is nil, it is only an internal message of the Overlay. + Wrap(msg interface{}, info *OverlayMessage) (interface{}, error) + // Unwrap takes the message coming from the network and returns the + // inner message that is going to be dispatched to the ProtocolInstance, the + // OverlayMessage needed by the Overlay to function correctly and then any + // error that might have occurred. + Unwrap(msg interface{}) (interface{}, *OverlayMessage, error) + // PacketType returns the packet type ID that this Protocol expects from the + // network. This is needed in order for the Overlay to receive those + // messages and dispatch them to the correct MessageProxy. + PacketType() network.PacketTypeID + // Name returns the name associated with this MessageProxy. When creating a + // protocol, if one use a name used by a MessageProxy, this MessageProxy will be + // used to Wrap and Unwrap messages. + Name() string +} + +// NewMessageProxy is a function typedef to instantiate a new MessageProxy. +type NewMessageProxy func() MessageProxy + +type messageProxyFactoryStruct struct { + factories []NewMessageProxy +} + +var messageProxyFactory = messageProxyFactoryStruct{} + +// RegisterMessageProxy saves a new NewMessageProxy under its name. +// When a Conode is instantiated, all MessageProxys will be generated and stored +// for this Conode. +func RegisterMessageProxy(n NewMessageProxy) { + messageProxyFactory.factories = append(messageProxyFactory.factories, n) +} + +// messageProxyStore contains all created MessageProxys. It contains the default +// MessageProxy used by the Overlay for backwards-compatibility. +type messageProxyStore struct { + sync.Mutex + protos []MessageProxy + defaultIO MessageProxy +} + +func (p *messageProxyStore) getByName(name string) MessageProxy { + p.Lock() + defer p.Unlock() + for _, pio := range p.protos { + if pio.Name() == name { + return pio + } + } + return p.defaultIO +} + +func (p *messageProxyStore) getByPacketType(t network.PacketTypeID) MessageProxy { + p.Lock() + defer p.Unlock() + for _, pio := range p.protos { + if pio.PacketType().Equal(t) { + return pio + } + } + return p.defaultIO +} + +func newMessageProxyStore(disp network.Dispatcher, proc network.Processor) *messageProxyStore { + pstore := &messageProxyStore{ + // also add the default one + defaultIO: new(defaultProtoIO), + } + for name, newIO := range messageProxyFactory.factories { + io := newIO() + pstore.protos = append(pstore.protos, io) + disp.RegisterProcessor(proc, io.PacketType()) + log.Lvl2("Instantiating MessageProxy", name, "at position", len(pstore.protos)) + } + return pstore +} diff --git a/sda/protocol_test.go b/sda/protocol_test.go index 6e944b7e..7870fb79 100644 --- a/sda/protocol_test.go +++ b/sda/protocol_test.go @@ -8,12 +8,15 @@ import ( "github.com/dedis/cothority/log" "github.com/dedis/cothority/network" "github.com/satori/go.uuid" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var testProto = "test" -var simpleProto = "simple" +func init() { + network.RegisterPacketType(SimpleMessage{}) +} // ProtocolTest is the most simple protocol to be implemented, ignoring // everything it receives. @@ -48,7 +51,8 @@ func (p *ProtocolTest) Start() error { type SimpleProtocol struct { // chan to get back to testing - Chan chan bool + Chan chan bool + Error error *TreeNodeInstance } @@ -63,10 +67,7 @@ func (p *SimpleProtocol) Start() error { } // Dispatch analyses the message and does nothing else -func (p *SimpleProtocol) ReceiveMessage(msg struct { - *TreeNode - SimpleMessage -}) error { +func (p *SimpleProtocol) ReceiveMessage(msg MsgSimpleMessage) error { if msg.I != 10 { return errors.New("Not the value expected") } @@ -74,10 +75,26 @@ func (p *SimpleProtocol) ReceiveMessage(msg struct { return nil } +// ReturnError sends a message to the parent, and if it's the parent +// receiving the message, it triggers the channel +func (p *SimpleProtocol) ReturnError(msg MsgSimpleMessage) error { + if msg.I == 10 { + p.SendToParent(&SimpleMessage{9}) + } else { + p.Chan <- true + } + return p.Error +} + type SimpleMessage struct { I int } +type MsgSimpleMessage struct { + *TreeNode + SimpleMessage +} + // Test simple protocol-implementation // - registration func TestProtocolRegistration(t *testing.T) { @@ -102,6 +119,8 @@ func TestProtocolRegistration(t *testing.T) { // and start a protocol. H1 should receive that message and request the entitity // list and the treelist and then instantiate the protocol. func TestProtocolAutomaticInstantiation(t *testing.T) { + var simpleProto = "simpleAI" + // setup chanH1 := make(chan bool) chanH2 := make(chan bool) @@ -113,12 +132,11 @@ func TestProtocolAutomaticInstantiation(t *testing.T) { TreeNodeInstance: n, Chan: chans[id], } - ps.RegisterHandler(ps.ReceiveMessage) + log.ErrFatal(ps.RegisterHandler(ps.ReceiveMessage)) id++ return &ps, nil } - network.RegisterPacketType(SimpleMessage{}) GlobalProtocolRegister(simpleProto, fn) local := NewLocalTest() defer local.CloseAll() @@ -139,3 +157,191 @@ func TestProtocolAutomaticInstantiation(t *testing.T) { // entity list from h1 <-chanH2 } + +func TestProtocolError(t *testing.T) { + var simpleProto = "simplePE" + done := make(chan bool) + // The simplePE-protocol sends a message from the root to its + // children, which sends a message back and returns an error. + // When the root receives the message back, the second message + // is sent through the 'done'-channel. Like this we're sure that + // the children-message-handler had the time to return an error. + var protocolError error + fn := func(n *TreeNodeInstance) (ProtocolInstance, error) { + ps := SimpleProtocol{ + TreeNodeInstance: n, + Chan: done, + } + ps.Error = protocolError + log.ErrFatal(ps.RegisterHandler(ps.ReturnError)) + return &ps, nil + } + + GlobalProtocolRegister(simpleProto, fn) + local := NewLocalTest() + h, _, tree := local.GenTree(2, true) + h1 := h[0] + + oldlvl := log.DebugVisible() + // The error won't show if the DebugVisible is < 1 + if oldlvl < 1 { + log.SetDebugVisible(1) + } + // Redirecting stderr, so we can catch the error + log.OutputToBuf() + + // start the protocol + go func() { + _, err := h1.StartProtocol(simpleProto, tree) + if err != nil { + t.Fatal(fmt.Sprintf("Could not start protocol %v", err)) + } + }() + // Start is finished + <-done + // Return message is received + <-done + assert.Equal(t, "", log.GetStdErr(), "This should yield no error") + + protocolError = errors.New("Protocol Error") + // start the protocol + go func() { + _, err := h1.StartProtocol(simpleProto, tree) + if err != nil { + t.Fatal(fmt.Sprintf("Could not start protocol %v", err)) + } + }() + // Start is finished + <-done + // Return message is received + <-done + local.CloseAll() + + str := log.GetStdErr() + assert.NotEqual(t, "", str, "No error output") + log.OutputToOs() + + log.SetDebugVisible(oldlvl) +} + +func TestMessageProxyFactory(t *testing.T) { + defer eraseAllMessageProxy() + RegisterMessageProxy(NewTestMessageProxyChan) + assert.True(t, len(messageProxyFactory.factories) == 1) +} + +func TestMessageProxyStore(t *testing.T) { + defer eraseAllMessageProxy() + local := NewLocalTest() + defer local.CloseAll() + + RegisterMessageProxy(NewTestMessageProxy) + GlobalProtocolRegister(testProtoIOName, newTestProtocolInstance) + h, _, tree := local.GenTree(2, true) + + go func() { + // first time to wrap + res := <-chanProtoIOFeedback + require.Equal(t, "", res) + // second time to unwrap + res = <-chanProtoIOFeedback + require.Equal(t, "", res) + + }() + _, err := h[0].StartProtocol(testProtoIOName, tree) + require.Nil(t, err) + + res := <-chanTestProtoInstance + assert.True(t, res) +} + +// MessageProxy part +var chanProtoIOCreation = make(chan bool) +var chanProtoIOFeedback = make(chan string) + +const testProtoIOName = "TestIO" + +type OuterPacket struct { + Info *OverlayMessage + Inner *SimpleMessage +} + +var OuterPacketType = network.RegisterPacketType(OuterPacket{}) + +type TestMessageProxy struct{} + +func NewTestMessageProxyChan() MessageProxy { + chanProtoIOCreation <- true + return &TestMessageProxy{} +} + +func NewTestMessageProxy() MessageProxy { + return &TestMessageProxy{} +} + +func eraseAllMessageProxy() { + messageProxyFactory.factories = nil +} + +func (t *TestMessageProxy) Wrap(msg interface{}, info *OverlayMessage) (interface{}, error) { + outer := &OuterPacket{} + inner, ok := msg.(*SimpleMessage) + if !ok { + chanProtoIOFeedback <- "wrong message type in wrap" + } + outer.Inner = inner + outer.Info = info + chanProtoIOFeedback <- "" + return outer, nil +} + +func (t *TestMessageProxy) Unwrap(msg interface{}) (interface{}, *OverlayMessage, error) { + if msg == nil { + chanProtoIOFeedback <- "message nil!" + return nil, nil, errors.New("message nil") + } + + real, ok := msg.(OuterPacket) + if !ok { + chanProtoIOFeedback <- "wrong type of message in unwrap" + return nil, nil, errors.New("wrong message") + } + chanProtoIOFeedback <- "" + return real.Inner, real.Info, nil +} + +func (t *TestMessageProxy) PacketType() network.PacketTypeID { + return OuterPacketType +} + +func (t *TestMessageProxy) Name() string { + return testProtoIOName +} + +var chanTestProtoInstance = make(chan bool) + +// ProtocolInstance part +type TestProtocolInstance struct { + *TreeNodeInstance +} + +func newTestProtocolInstance(n *TreeNodeInstance) (ProtocolInstance, error) { + pi := &TestProtocolInstance{n} + n.RegisterHandler(pi.handleSimpleMessage) + return pi, nil +} + +func (t *TestProtocolInstance) Start() error { + t.SendTo(t.Root(), &SimpleMessage{12}) + return nil +} + +type SimpleMessageHandler struct { + *TreeNode + SimpleMessage +} + +func (t TestProtocolInstance) handleSimpleMessage(h SimpleMessageHandler) error { + chanTestProtoInstance <- h.SimpleMessage.I == 12 + return nil +} diff --git a/sda/sda_test.go b/sda/sda_test.go index 8308415a..aa2d8d71 100644 --- a/sda/sda_test.go +++ b/sda/sda_test.go @@ -12,7 +12,7 @@ import ( // To avoid setting up testing-verbosity in all tests func TestMain(m *testing.M) { - log.MainTest(m, 3) + log.MainTest(m) } // Returns a fresh ServerIdentity + private key with the diff --git a/sda/service.go b/sda/service.go index b7552311..e1991f71 100644 --- a/sda/service.go +++ b/sda/service.go @@ -88,7 +88,7 @@ var ServiceFactory = serviceFactory{ // mapping and the creation function. func (s *serviceFactory) Register(name string, fn NewServiceFunc) error { if s.ServiceID(name) != NilServiceID { - return fmt.Errorf("Service %s already registered.", name) + return fmt.Errorf("service %s already registered", name) } id := ServiceID(uuid.NewV5(uuid.NamespaceURL, name)) s.mutex.Lock() @@ -282,6 +282,14 @@ func (s *serviceManager) RegisterProcessor(p network.Processor, msgType network. s.Dispatcher.RegisterProcessor(p, msgType) } +func (s *serviceManager) RegisterProcessorFunc(msgType network.PacketTypeID, fn func(*network.Packet)) { + // delegate message to host so the host will pass the message to ourself + s.conode.RegisterProcessor(s, msgType) + // handle the message ourselves (will be launched in a go routine) + s.Dispatcher.RegisterProcessorFunc(msgType, fn) + +} + // AvailableServices returns a list of all services available to the serviceManager. // If no services are instantiated, it returns an empty list. func (s *serviceManager) AvailableServices() (ret []string) { @@ -342,34 +350,6 @@ func CreateClientRequest(service string, r interface{}) (*ClientRequest, error) }, nil } -// InterServiceMessage is a generic struct that contains any data destined to a -// Service that has been created .. by a Service. => Intra-Service -// communications. -type InterServiceMessage struct { - // Service is the ID of the Service it's destined - Service ServiceID - // Data is the data encoded using protobuf for the moment. - Data []byte -} - -// ServiceMessageID is the ID of the ServiceMessage struct. -var ServiceMessageID = network.RegisterPacketType(InterServiceMessage{}) - -// CreateServiceMessage takes a service name and some data and encodes the whole -// as a ServiceMessage. -func CreateServiceMessage(service string, r interface{}) (*InterServiceMessage, error) { - sid := ServiceFactory.ServiceID(service) - buff, err := network.MarshalRegisteredType(r) - if err != nil { - return nil, err - } - return &InterServiceMessage{ - Service: sid, - Data: buff, - }, nil - -} - // Client is a struct used to communicate with a remote Service running on a // sda.Conode type Client struct { diff --git a/sda/service_test.go b/sda/service_test.go index 55f5e172..314ee2c4 100644 --- a/sda/service_test.go +++ b/sda/service_test.go @@ -9,8 +9,16 @@ import ( "github.com/dedis/cothority/log" "github.com/dedis/cothority/network" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) +func init() { + network.RegisterPacketType(SimpleMessageForth{}) + network.RegisterPacketType(SimpleMessageBack{}) + dummyMsgType = network.RegisterPacketType(DummyMsg{}) + RegisterNewService("ISMService", newServiceMessages) +} + func TestServiceRegistration(t *testing.T) { var name = "dummy" RegisterNewService(name, func(c *Context, path string) Service { @@ -36,99 +44,6 @@ func TestServiceRegistration(t *testing.T) { } } -type DummyProtocol struct { - *TreeNodeInstance - link chan bool - config DummyConfig -} - -type DummyConfig struct { - A int - Send bool -} - -type DummyMsg struct { - A int -} - -var dummyMsgType network.PacketTypeID - -func init() { - dummyMsgType = network.RegisterPacketType(DummyMsg{}) -} - -func NewDummyProtocol(tni *TreeNodeInstance, conf DummyConfig, link chan bool) *DummyProtocol { - return &DummyProtocol{tni, link, conf} -} - -func (dm *DummyProtocol) Start() error { - dm.link <- true - if dm.config.Send { - // also send to the children if any - if !dm.IsLeaf() { - if err := dm.SendToChildren(&DummyMsg{}); err != nil { - log.Error(err) - } - } - } - return nil -} - -func (dm *DummyProtocol) ProcessProtocolMsg(msg *ProtocolMsg) { - dm.link <- true -} - -// legcy reasons -func (dm *DummyProtocol) Dispatch() error { - return nil -} - -type DummyService struct { - c *Context - path string - link chan bool - fakeTree *Tree - firstTni *TreeNodeInstance - Config DummyConfig -} - -func (ds *DummyService) ProcessClientRequest(si *network.ServerIdentity, r *ClientRequest) { - msgT, _, err := network.UnmarshalRegisteredType(r.Data, network.DefaultConstructors(network.Suite)) - if err != nil || msgT != dummyMsgType { - ds.link <- false - return - } - if ds.firstTni == nil { - ds.firstTni = ds.c.NewTreeNodeInstance(ds.fakeTree, ds.fakeTree.Root, "DummyService") - } - - dp := NewDummyProtocol(ds.firstTni, ds.Config, ds.link) - - if err := ds.c.RegisterProtocolInstance(dp); err != nil { - ds.link <- false - return - } - dp.Start() -} - -func (ds *DummyService) NewProtocol(tn *TreeNodeInstance, conf *GenericConfig) (ProtocolInstance, error) { - dp := NewDummyProtocol(tn, DummyConfig{}, ds.link) - return dp, nil -} - -func (ds *DummyService) Process(packet *network.Packet) { - if packet.MsgType != dummyMsgType { - ds.link <- false - return - } - dms := packet.Msg.(DummyMsg) - if dms.A != 10 { - ds.link <- false - return - } - ds.link <- true -} - func TestServiceNew(t *testing.T) { ds := &DummyService{ link: make(chan bool), @@ -338,24 +253,6 @@ func TestServiceProcessor(t *testing.T) { log.ErrFatal(ServiceFactory.Unregister("DummyService")) } -type clientProc struct { - t *testing.T - relay chan SimpleResponse -} - -func newClientProc(t *testing.T) *clientProc { - return &clientProc{ - relay: make(chan SimpleResponse), - } -} - -func (c *clientProc) Process(p *network.Packet) { - if p.MsgType != SimpleResponseType { - c.t.Fatal("Message type not SimpleResponseType") - } - c.relay <- p.Msg.(SimpleResponse) -} - func TestServiceBackForthProtocol(t *testing.T) { local := NewLocalTest() defer local.CloseAll() @@ -490,6 +387,18 @@ func TestServiceManager_Service(t *testing.T) { assert.NotNil(t, service, "Didn't find service testService") } +func TestServiceMessages(t *testing.T) { + local := NewLocalTest() + defer local.CloseAll() + conodes, _, _ := local.GenTree(2, true) + + service := conodes[0].serviceManager.Service("ISMService") + assert.NotNil(t, service, "Didn't find service ISMService") + ism := service.(*ServiceMessages) + ism.SendRaw(conodes[0].ServerIdentity, &SimpleResponse{}) + require.True(t, <-ism.GotResponse, "Didn't get response") +} + // BackForthProtocolForth & Back are messages that go down and up the tree. // => BackForthProtocol protocol / message type SimpleMessageForth struct { @@ -500,9 +409,6 @@ type SimpleMessageBack struct { Val int } -var simpleMessageForthType = network.RegisterPacketType(SimpleMessageForth{}) -var simpleMessageBackType = network.RegisterPacketType(SimpleMessageBack{}) - type BackForthProtocol struct { *TreeNodeInstance Val int @@ -639,6 +545,117 @@ func (s *simpleService) Process(packet *network.Packet) { return } +type DummyProtocol struct { + *TreeNodeInstance + link chan bool + config DummyConfig +} + +type DummyConfig struct { + A int + Send bool +} + +type DummyMsg struct { + A int +} + +var dummyMsgType network.PacketTypeID + +func newDummyProtocol(tni *TreeNodeInstance, conf DummyConfig, link chan bool) *DummyProtocol { + return &DummyProtocol{tni, link, conf} +} + +func (dm *DummyProtocol) Start() error { + dm.link <- true + if dm.config.Send { + // also send to the children if any + if !dm.IsLeaf() { + if err := dm.SendToChildren(&DummyMsg{}); err != nil { + log.Error(err) + } + } + } + return nil +} + +func (dm *DummyProtocol) ProcessProtocolMsg(msg *ProtocolMsg) { + dm.link <- true +} + +// legcy reasons +func (dm *DummyProtocol) Dispatch() error { + return nil +} + +type DummyService struct { + c *Context + path string + link chan bool + fakeTree *Tree + firstTni *TreeNodeInstance + Config DummyConfig +} + +func (ds *DummyService) ProcessClientRequest(si *network.ServerIdentity, r *ClientRequest) { + msgT, _, err := network.UnmarshalRegisteredType(r.Data, network.DefaultConstructors(network.Suite)) + if err != nil || msgT != dummyMsgType { + ds.link <- false + return + } + if ds.firstTni == nil { + ds.firstTni = ds.c.NewTreeNodeInstance(ds.fakeTree, ds.fakeTree.Root, "DummyService") + } + + dp := newDummyProtocol(ds.firstTni, ds.Config, ds.link) + + if err := ds.c.RegisterProtocolInstance(dp); err != nil { + ds.link <- false + return + } + dp.Start() +} + +func (ds *DummyService) NewProtocol(tn *TreeNodeInstance, conf *GenericConfig) (ProtocolInstance, error) { + dp := newDummyProtocol(tn, DummyConfig{}, ds.link) + return dp, nil +} + +func (ds *DummyService) Process(packet *network.Packet) { + if packet.MsgType != dummyMsgType { + ds.link <- false + return + } + dms := packet.Msg.(DummyMsg) + if dms.A != 10 { + ds.link <- false + return + } + ds.link <- true +} + +type ServiceMessages struct { + *ServiceProcessor + GotResponse chan bool +} + +func (i *ServiceMessages) SimpleResponse(msg *network.Packet) { + i.GotResponse <- true +} + +func (i *ServiceMessages) NewProtocol(tn *TreeNodeInstance, conf *GenericConfig) (ProtocolInstance, error) { + return nil, nil +} + +func newServiceMessages(c *Context, path string) Service { + s := &ServiceMessages{ + ServiceProcessor: NewServiceProcessor(c), + GotResponse: make(chan bool), + } + c.RegisterProcessorFunc(SimpleResponseType, s.SimpleResponse) + return s +} + func waitOrFatalValue(ch chan bool, v bool, t *testing.T) { select { case b := <-ch: diff --git a/sda/treenode.go b/sda/treenode.go index 75718601..fcbf178b 100644 --- a/sda/treenode.go +++ b/sda/treenode.go @@ -46,6 +46,8 @@ type TreeNodeInstance struct { msgDispatchQueueWait chan bool // whether this node is closing closing bool + + protoIO MessageProxy } // aggregateMessages (if set) tells to aggregate messages from all children @@ -59,7 +61,7 @@ const ( type MsgHandler func([]*interface{}) // NewNode creates a new node -func newTreeNodeInstance(o *Overlay, tok *Token, tn *TreeNode) *TreeNodeInstance { +func newTreeNodeInstance(o *Overlay, tok *Token, tn *TreeNode, io MessageProxy) *TreeNodeInstance { n := &TreeNodeInstance{overlay: o, token: tok, channels: make(map[network.PacketTypeID]interface{}), @@ -69,6 +71,7 @@ func newTreeNodeInstance(o *Overlay, tok *Token, tn *TreeNode) *TreeNodeInstance treeNode: tn, msgDispatchQueue: make([]*ProtocolMsg, 0, 1), msgDispatchQueueWait: make(chan bool, 1), + protoIO: io, } go n.dispatchMsgReader() return n @@ -115,7 +118,7 @@ func (n *TreeNodeInstance) SendTo(to *TreeNode, msg interface{}) error { if to == nil { return errors.New("Sent to a nil TreeNode") } - return n.overlay.SendToTreeNode(n.token, to, msg) + return n.overlay.SendToTreeNode(n.token, to, msg, n.protoIO) } // Tree returns the tree of that node @@ -205,24 +208,30 @@ func (n *TreeNodeInstance) RegisterHandler(c interface{}) error { if cr.Kind() != reflect.Func { return errors.New("Input is not function") } - cr = cr.In(0) - if cr.Kind() == reflect.Slice { + if cr.NumOut() != 1 { + return errors.New("Need exactly one return argument of type error") + } + if cr.Out(0) != reflect.TypeOf((*error)(nil)).Elem() { + return errors.New("return-type of message-handler needs to be error") + } + ci := cr.In(0) + if ci.Kind() == reflect.Slice { flags += AggregateMessages - cr = cr.Elem() + ci = ci.Elem() } - if cr.Kind() != reflect.Struct { - return errors.New("Input is not channel of structure") + if ci.Kind() != reflect.Struct { + return errors.New("Input is not a structure") } - if cr.NumField() != 2 { - return errors.New("Input is not channel of structure with 2 elements") + if ci.NumField() != 2 { + return errors.New("Input is not a structure with 2 elements") } - if cr.Field(0).Type != reflect.TypeOf(&TreeNode{}) { - return errors.New("Input-channel doesn't have TreeNode as element") + if ci.Field(0).Type != reflect.TypeOf(&TreeNode{}) { + return errors.New("Input-handler doesn't have TreeNode as element") } // Automatic registration of the message to the network library. typ := network.RegisterPacketUUID(network.RTypeToPacketTypeID( - cr.Field(1).Type), - cr.Field(1).Type) + ci.Field(1).Type), + ci.Field(1).Type) //typ := network.RTypeToUUID(cr.Elem().Field(1).Type) n.handlers[typ] = c n.messageTypeFlags[typ] = flags @@ -278,21 +287,31 @@ func (n *TreeNodeInstance) dispatchHandler(msgSlice []*ProtocolMsg) error { mt := msgSlice[0].MsgType to := reflect.TypeOf(n.handlers[mt]).In(0) f := reflect.ValueOf(n.handlers[mt]) + var errV reflect.Value if n.HasFlag(mt, AggregateMessages) { msgs := reflect.MakeSlice(to, len(msgSlice), len(msgSlice)) for i, msg := range msgSlice { msgs.Index(i).Set(n.reflectCreate(to.Elem(), msg)) } log.Lvl4("Dispatching aggregation to", n.ServerIdentity().Address) - f.Call([]reflect.Value{msgs}) + errV = f.Call([]reflect.Value{msgs})[0] } else { for _, msg := range msgSlice { - log.Lvl4("Dispatching to", n.ServerIdentity().Address) + if errV.IsValid() && !errV.IsNil() { + // Before overwriting an error, print it out + log.Errorf("%s: error while dispatching message %s: %s", + n.Name(), reflect.TypeOf(msg.Msg), + errV.Interface().(error)) + } + log.Lvl4("Dispatching", msg, "to", n.ServerIdentity().Address) m := n.reflectCreate(to, msg) - f.Call([]reflect.Value{m}) + errV = f.Call([]reflect.Value{m})[0] } } log.Lvlf4("%s Done with handler for %s", n.Name(), f.Type()) + if !errV.IsNil() { + return errV.Interface().(error) + } return nil } @@ -361,7 +380,8 @@ func (n *TreeNodeInstance) dispatchMsgReader() { n.msgDispatchQueueMutex.Unlock() err := n.dispatchMsgToProtocol(msg) if err != nil { - log.Error("Error while dispatching message:", err) + log.Errorf("%s: error while dispatching message %s: %s", + n.Name(), reflect.TypeOf(msg.Msg), err) } } else { n.msgDispatchQueueMutex.Unlock() @@ -373,19 +393,6 @@ func (n *TreeNodeInstance) dispatchMsgReader() { // dispatchMsgToProtocol will dispatch this sda.Data to the right instance func (n *TreeNodeInstance) dispatchMsgToProtocol(sdaMsg *ProtocolMsg) error { - // Decode the inner message here. In older versions, it was decoded before, - // but first there is no use to do it before, and then every protocols had - // to manually registers their messages. Since it is done automatically by - // the Node, decoding should also be done by the node. - var err error - t, msg, err := network.UnmarshalRegisteredType(sdaMsg.MsgSlice, network.DefaultConstructors(n.Suite())) - if err != nil { - log.Error(n.ServerIdentity(), "Error while unmarshalling inner message of SDAData", sdaMsg.MsgType, ":", err) - } - // Put the msg into SDAData - sdaMsg.MsgType = t - sdaMsg.Msg = msg - // if message comes from parent, dispatch directly // if messages come from children we must aggregate them // if we still need to wait for additional messages, we return @@ -396,6 +403,7 @@ func (n *TreeNodeInstance) dispatchMsgToProtocol(sdaMsg *ProtocolMsg) error { } log.Lvlf5("%s->%s: Message is: %+v", n.Name(), sdaMsg.Msg) + var err error switch { case n.channels[msgType] != nil: log.Lvl4(n.Name(), "Dispatching to channel") @@ -404,8 +412,7 @@ func (n *TreeNodeInstance) dispatchMsgToProtocol(sdaMsg *ProtocolMsg) error { log.Lvl4("Dispatching to handler", n.ServerIdentity().Address) err = n.dispatchHandler(msgs) default: - log.Error("This message-type is not handled by this protocol") - return errors.New("This message-type is not handled by this protocol") + return fmt.Errorf("message-type not handled the protocol: %s", reflect.TypeOf(sdaMsg.Msg)) } return err } diff --git a/sda/treenode_test.go b/sda/treenode_test.go index ce3f5af6..10db3bc7 100644 --- a/sda/treenode_test.go +++ b/sda/treenode_test.go @@ -4,10 +4,14 @@ import ( "testing" "github.com/dedis/cothority/log" + "github.com/stretchr/testify/assert" ) -func TestTreeNodeCreateProtocol(t *testing.T) { +func init() { GlobalProtocolRegister(spawnName, newSpawnProto) +} + +func TestTreeNodeCreateProtocol(t *testing.T) { local := NewLocalTest() defer local.CloseAll() @@ -24,6 +28,19 @@ func TestTreeNodeCreateProtocol(t *testing.T) { <-spawnCh } +func TestHandlerReturn(t *testing.T) { + local := NewLocalTest() + defer local.CloseAll() + + hosts, _, tree := local.GenTree(1, true) + pi, err := hosts[0].overlay.CreateProtocolSDA(spawnName, tree) + log.ErrFatal(err) + p := pi.(*spawnProto) + assert.NotNil(t, p.RegisterHandler(p.HandlerError1)) + assert.Nil(t, p.RegisterHandler(p.HandlerError2)) + assert.NotNil(t, p.RegisterHandler(p.HandlerError3)) +} + // spawnCh is used to dispatch information from a spawnProto to the test var spawnCh = make(chan bool) @@ -55,55 +72,20 @@ func (s *spawnProto) Start() error { return nil } -func TestTreeNodeSendToItSelf(t *testing.T) { - log.TestOutput(true, 5) - GlobalProtocolRegister(ownName, newOwnProto) - local := NewLocalTest() - defer local.CloseAll() - - hosts, _, tree := local.GenTree(1, true) - pi, err := hosts[0].overlay.CreateProtocolSDA(ownName, tree) - log.ErrFatal(err) - go pi.Start() - - log.Print("waiting on ownCh") - <-ownCh - -} - -var ownCh = make(chan bool) - -const ownName = "Own" - -type OwnMessage struct { - Val int -} - -type wrapOwn struct { +type spawnMsg struct { *TreeNode - OwnMessage + I int } -// ownProto is a protocol that sends a message to itself -type ownProto struct { - *TreeNodeInstance -} +// Invalid handler +func (s *spawnProto) HandlerError1(msg spawnMsg) {} -func newOwnProto(tn *TreeNodeInstance) (ProtocolInstance, error) { - o := &ownProto{tn} - o.RegisterHandler(o.receiveOwnMsg) - return o, nil -} - -func (o *ownProto) Start() error { - log.Print("Sending to ourself") - err := o.SendTo(o.TreeNode(), &OwnMessage{12}) - log.Print("Sent to ourself DONE", err) +// Valid handler +func (s *spawnProto) HandlerError2(msg spawnMsg) error { return nil } -func (o *ownProto) receiveOwnMsg(wrap wrapOwn) error { - ownCh <- true - o.Done() - return nil +// Invalid handler +func (s *spawnProto) HandlerError3(msg spawnMsg) (int, error) { + return 0, nil } diff --git a/services/cosi/api.go b/services/cosi/api.go new file mode 100644 index 00000000..a95ce038 --- /dev/null +++ b/services/cosi/api.go @@ -0,0 +1,42 @@ +package service + +import ( + "errors" + + "github.com/dedis/cothority/log" + "github.com/dedis/cothority/sda" +) + +// Client is a structure to communicate with the CoSi +// service +type Client struct { + *sda.Client +} + +// NewClient instantiates a new cosi.Client +func NewClient() *Client { + return &Client{Client: sda.NewClient(ServiceName)} +} + +// SignMsg sends a CoSi sign request to the Cothority defined by the given +// Roster +func (c *Client) SignMsg(r *sda.Roster, msg []byte) (*SignatureResponse, error) { + serviceReq := &SignatureRequest{ + Roster: r, + Message: msg, + } + if len(r.List) == 0 { + return nil, errors.New("Got an empty roster-list") + } + dst := r.List[0] + log.Lvl4("Sending message to", dst) + reply, err := c.Send(dst, serviceReq) + if err != nil { + return nil, err + } + sr, ok := reply.Msg.(SignatureResponse) + if !ok { + return nil, errors.New("this is odd: couldn't cast reply") + } + return &sr, nil +} diff --git a/services/cosi/cosi.go b/services/cosi/cosi.go new file mode 100644 index 00000000..5672cebd --- /dev/null +++ b/services/cosi/cosi.go @@ -0,0 +1,100 @@ +package service + +import ( + "errors" + + "fmt" + "time" + + "github.com/dedis/cothority/crypto" + "github.com/dedis/cothority/log" + "github.com/dedis/cothority/network" + "github.com/dedis/cothority/protocols/cosi" + "github.com/dedis/cothority/sda" +) + +// This file contains all the code to run a CoSi service. It is used to reply to +// client request for signing something using CoSi. +// As a prototype, it just signs and returns. It would be very easy to write an +// updated version that chains all signatures for example. + +// ServiceName is the name to refer to the CoSi service +const ServiceName = "CoSi" + +func init() { + sda.RegisterNewService(ServiceName, newCoSiService) + network.RegisterPacketType(&SignatureRequest{}) + network.RegisterPacketType(&SignatureResponse{}) +} + +// CoSi is the service that handles collective signing operations +type CoSi struct { + *sda.ServiceProcessor + path string +} + +// SignatureRequest is what the Cosi service is expected to receive from clients. +type SignatureRequest struct { + Message []byte + Roster *sda.Roster +} + +// SignatureResponse is what the Cosi service will reply to clients. +type SignatureResponse struct { + Sum []byte + Signature []byte +} + +// SignatureRequest treats external request to this service. +func (cs *CoSi) SignatureRequest(si *network.ServerIdentity, req *SignatureRequest) (network.Body, error) { + tree := req.Roster.GenerateBinaryTree() + tni := cs.NewTreeNodeInstance(tree, tree.Root, cosi.Name) + pi, err := cosi.NewProtocol(tni) + if err != nil { + return nil, errors.New("Couldn't make new protocol: " + err.Error()) + } + cs.RegisterProtocolInstance(pi) + pcosi := pi.(*cosi.CoSi) + pcosi.SigningMessage(req.Message) + h, err := crypto.HashBytes(network.Suite.Hash(), req.Message) + if err != nil { + return nil, errors.New("Couldn't hash message: " + err.Error()) + } + response := make(chan []byte) + pcosi.RegisterSignatureHook(func(sig []byte) { + response <- sig + }) + log.Lvl3("Cosi Service starting up root protocol") + go pi.Dispatch() + go pi.Start() + sig := <-response + if log.DebugVisible() > 1 { + fmt.Printf("%s: Signed a message.\n", time.Now().Format("Mon Jan 2 15:04:05 -0700 MST 2006")) + } + return &SignatureResponse{ + Sum: h, + Signature: sig, + }, nil +} + +// NewProtocol is called on all nodes of a Tree (except the root, since it is +// the one starting the protocol) so it's the Service that will be called to +// generate the PI on all others node. +func (cs *CoSi) NewProtocol(tn *sda.TreeNodeInstance, conf *sda.GenericConfig) (sda.ProtocolInstance, error) { + log.Lvl3("Cosi Service received New Protocol event") + pi, err := cosi.NewProtocol(tn) + go pi.Dispatch() + return pi, err +} + +func newCoSiService(c *sda.Context, path string) sda.Service { + s := &CoSi{ + ServiceProcessor: sda.NewServiceProcessor(c), + path: path, + } + err := s.RegisterMessage(s.SignatureRequest) + if err != nil { + log.ErrFatal(err, "Couldn't register message:") + } + return s +} diff --git a/services/cosi/cosi_test.go b/services/cosi/cosi_test.go new file mode 100644 index 00000000..7f61007c --- /dev/null +++ b/services/cosi/cosi_test.go @@ -0,0 +1,35 @@ +package service + +import ( + "testing" + + "github.com/dedis/cothority/log" + "github.com/dedis/cothority/sda" + "github.com/dedis/crypto/cosi" + "github.com/stretchr/testify/assert" +) + +func NewTestClient(l *sda.LocalTest) *Client { + return &Client{Client: l.NewClient(ServiceName)} +} + +func TestServiceCosi(t *testing.T) { + defer log.AfterTest(t) + log.TestOutput(testing.Verbose(), 4) + local := sda.NewLocalTest() + // generate 5 hosts, they don't connect, they process messages, and they + // don't register the tree or entitylist + hosts, el, _ := local.GenTree(5, false) + defer local.CloseAll() + + // Send a request to the service + client := NewTestClient(local) + msg := []byte("hello cosi service") + log.Lvl1("Sending request to service...") + res, err := client.SignMsg(el, msg) + log.ErrFatal(err, "Couldn't send") + + // verify the response still + assert.Nil(t, cosi.VerifySignature(hosts[0].Suite(), el.Publics(), + msg, res.Signature)) +} diff --git a/services/identity/service.go b/services/identity/service.go index e23e8a2f..5ea76892 100644 --- a/services/identity/service.go +++ b/services/identity/service.go @@ -43,9 +43,12 @@ func init() { type Service struct { *sda.ServiceProcessor *StorageMap - identitiesMutex sync.Mutex - skipchain *skipchain.Client - path string + propagateIdentity manage.PropagationFunc + propagateSkipBlock manage.PropagationFunc + propagateConfig manage.PropagationFunc + identitiesMutex sync.Mutex + skipchain *skipchain.Client + path string } // StorageMap holds the map to the storages so it can be marshaled. @@ -63,18 +66,8 @@ type Storage struct { Data *skipchain.SkipBlock } -// NewProtocol is called by the Overlay when a new protocol request comes in. +// NewProtocol is not used here. func (s *Service) NewProtocol(tn *sda.TreeNodeInstance, conf *sda.GenericConfig) (sda.ProtocolInstance, error) { - log.Lvl3(s.ServerIdentity(), "Identity received New Protocol event", conf) - switch tn.ProtocolName() { - case "Propagate": - pi, err := manage.NewPropagateProtocol(tn) - if err != nil { - return nil, err - } - pi.(*manage.Propagate).RegisterOnData(s.Propagate) - return pi, err - } return nil, nil } @@ -104,8 +97,7 @@ func (s *Service) CreateIdentity(si *network.ServerIdentity, ai *CreateIdentity) } roster := ids.Root.Roster - replies, err := manage.PropagateStartAndWait(s.Context, roster, - &PropagateIdentity{ids}, propagateTimeout, s.Propagate) + replies, err := s.propagateIdentity(roster, &PropagateIdentity{ids}, propagateTimeout) if err != nil { return nil, err } @@ -144,8 +136,7 @@ func (s *Service) ProposeSend(si *network.ServerIdentity, p *ProposeSend) (netwo return nil, errors.New("Didn't find Identity") } roster := sid.Root.Roster - replies, err := manage.PropagateStartAndWait(s.Context, roster, - p, propagateTimeout, s.Propagate) + replies, err := s.propagateConfig(roster, p, propagateTimeout) if err != nil { return nil, err } @@ -214,7 +205,7 @@ func (s *Service) ProposeVote(si *network.ServerIdentity, v *ProposeVote) (netwo } // Propagate the vote - _, err = manage.PropagateStartAndWait(s.Context, sid.Root.Roster, v, propagateTimeout, s.Propagate) + _, err = s.propagateConfig(sid.Root.Roster, v, propagateTimeout) if err != nil { return nil, err } @@ -236,8 +227,7 @@ func (s *Service) ProposeVote(si *network.ServerIdentity, v *ProposeVote) (netwo ID: v.ID, Latest: reply.Latest, } - _, err = manage.PropagateStartAndWait(s.Context, sid.Root.Roster, - usb, propagateTimeout, s.Propagate) + _, err = s.propagateSkipBlock(sid.Root.Roster, usb, propagateTimeout) if err != nil { return nil, err } @@ -251,8 +241,8 @@ func (s *Service) ProposeVote(si *network.ServerIdentity, v *ProposeVote) (netwo * Internal messages */ -// Propagate handles propagation of all data in the identity-service -func (s *Service) Propagate(msg network.Body) { +// propagateConfig handles propagation of all configuration-proposals in the identity-service. +func (s *Service) propagateConfigHandler(msg network.Body) { log.Lvlf4("Got msg %+v %v", msg, reflect.TypeOf(msg).String()) id := ID(nil) switch msg.(type) { @@ -260,17 +250,8 @@ func (s *Service) Propagate(msg network.Body) { id = msg.(*ProposeSend).ID case *ProposeVote: id = msg.(*ProposeVote).ID - case *UpdateSkipBlock: - id = msg.(*UpdateSkipBlock).ID - case *PropagateIdentity: - pi := msg.(*PropagateIdentity) - id = ID(pi.Data.Hash) - if s.getIdentityStorage(id) != nil { - log.Error("Couldn't store new identity") - return - } - log.Lvl3("Storing identity in", s) - s.setIdentityStorage(id, pi.Storage) + default: + log.Errorf("Got an unidentified propagation-request: %v", msg) return } @@ -290,25 +271,59 @@ func (s *Service) Propagate(msg network.Body) { case *ProposeVote: v := msg.(*ProposeVote) sid.Votes[v.Signer] = v.Signature - case *UpdateSkipBlock: - skipblock := msg.(*UpdateSkipBlock).Latest - _, msgLatest, err := network.UnmarshalRegistered(skipblock.Data) - if err != nil { - log.Error(err) - return - } - al, ok := msgLatest.(*Config) - if !ok { - log.Error(err) - return - } - sid.Data = skipblock - sid.Latest = al - sid.Proposed = nil } } } +// propagateSkipBlock saves a new skipblock to the identity +func (s *Service) propagateSkipBlockHandler(msg network.Body) { + log.Lvlf4("Got msg %+v %v", msg, reflect.TypeOf(msg).String()) + usb, ok := msg.(*UpdateSkipBlock) + if !ok { + log.Error("Wrong message-type") + return + } + sid := s.getIdentityStorage(usb.ID) + if sid == nil { + log.Error("Didn't find entity in", s) + return + } + sid.Lock() + defer sid.Unlock() + skipblock := msg.(*UpdateSkipBlock).Latest + _, msgLatest, err := network.UnmarshalRegistered(skipblock.Data) + if err != nil { + log.Error(err) + return + } + al, ok := msgLatest.(*Config) + if !ok { + log.Error(err) + return + } + sid.Data = skipblock + sid.Latest = al + sid.Proposed = nil +} + +// propagateIdentity stores a new identity in all nodes. +func (s *Service) propagateIdentityHandler(msg network.Body) { + log.Lvlf4("Got msg %+v %v", msg, reflect.TypeOf(msg).String()) + pi, ok := msg.(*PropagateIdentity) + if !ok { + log.Error("Got a wrong message for propagation") + return + } + id := ID(pi.Data.Hash) + if s.getIdentityStorage(id) != nil { + log.Error("Couldn't store new identity") + return + } + log.Lvl3("Storing identity in", s) + s.setIdentityStorage(id, pi.Storage) + return +} + // getIdentityStorage returns the corresponding IdentityStorage or nil // if none was found func (s *Service) getIdentityStorage(id ID) *Storage { @@ -373,6 +388,22 @@ func newIdentityService(c *sda.Context, path string) sda.Service { skipchain: skipchain.NewClient(), path: path, } + var err error + s.propagateIdentity, err = + manage.NewPropagationFunc(c, "IdentityPropagateID", s.propagateIdentityHandler) + if err != nil { + return nil + } + s.propagateSkipBlock, err = + manage.NewPropagationFunc(c, "IdentityPropagateSB", s.propagateSkipBlockHandler) + if err != nil { + return nil + } + s.propagateConfig, err = + manage.NewPropagationFunc(c, "IdentityPropagateConf", s.propagateConfigHandler) + if err != nil { + return nil + } if err := s.tryLoad(); err != nil { log.Error(err) } diff --git a/services/skipchain/skipchain.go b/services/skipchain/skipchain.go index dd6bb4fe..586df117 100644 --- a/services/skipchain/skipchain.go +++ b/services/skipchain/skipchain.go @@ -31,9 +31,6 @@ const skipchainBFT = "SkipchainBFT" func init() { sda.RegisterNewService(ServiceName, newSkipchainService) skipchainSID = sda.ServiceFactory.ServiceID(ServiceName) - sda.GlobalProtocolRegister(skipchainBFT, func(n *sda.TreeNodeInstance) (sda.ProtocolInstance, error) { - return bftcosi.NewBFTCoSiProtocol(n, nil) - }) network.RegisterPacketType(&SkipBlockMap{}) } @@ -47,6 +44,7 @@ type Service struct { // SkipBlocks points from SkipBlockID to SkipBlock but SkipBlockID is not a valid // key-type for maps, so we need to cast it to string *SkipBlockMap + Propagate manage.PropagationFunc gMutex sync.Mutex path string verifiers map[VerifierID]SkipBlockVerifier @@ -178,11 +176,11 @@ func (s *Service) SetChildrenSkipBlock(si *network.ServerIdentity, scsb *SetChil childID := scsb.ChildID parent, ok := s.getSkipBlockByID(parentID) if !ok { - return nil, errors.New("Couldn't find skipblock!") + return nil, errors.New("couldn't find skipblock") } child, ok := s.getSkipBlockByID(childID) if !ok { - return nil, errors.New("Couldn't find skipblock!") + return nil, errors.New("couldn't find skipblock") } child.ParentBlockID = parentID parent.ChildSL = NewBlockLink() @@ -204,19 +202,7 @@ func (s *Service) SetChildrenSkipBlock(si *network.ServerIdentity, scsb *SetChil // the one starting the protocol) so it's the Service that will be called to // generate the PI on all others node. func (s *Service) NewProtocol(tn *sda.TreeNodeInstance, conf *sda.GenericConfig) (sda.ProtocolInstance, error) { - var pi sda.ProtocolInstance - var err error - switch tn.ProtocolName() { - case "Propagate": - pi, err = manage.NewPropagateProtocol(tn) - if err != nil { - return nil, err - } - pi.(*manage.Propagate).RegisterOnData(s.PropagateSkipBlock) - case skipchainBFT: - pi, err = bftcosi.NewBFTCoSiProtocol(tn, s.bftVerify) - } - return pi, err + return nil, nil } // PropagateSkipBlock will save a new SkipBlock @@ -340,7 +326,7 @@ func (s *Service) startBFTSignature(block *SkipBlock) error { // Start the protocol tree := el.GenerateNaryTreeWithRoot(2, s.ServerIdentity()) - node, err := s.CreateProtocolService(skipchainBFT, tree) + node, err := s.CreateProtocolSDA(skipchainBFT, tree) if err != nil { return errors.New("Couldn't create new node: " + err.Error()) } @@ -433,8 +419,7 @@ func (s *Service) startPropagation(blocks []*SkipBlock) error { } roster = sb.Roster } - replies, err := manage.PropagateStartAndWait(s.Context, roster, - block, propagateTimeout, s.PropagateSkipBlock) + replies, err := s.Propagate(roster, block, propagateTimeout) if err != nil { return err } @@ -531,15 +516,17 @@ func newSkipchainService(c *sda.Context, path string) sda.Service { SkipBlockMap: &SkipBlockMap{make(map[string]*SkipBlock)}, verifiers: map[VerifierID]SkipBlockVerifier{}, } + var err error + s.Propagate, err = manage.NewPropagationFunc(c, "SkipchainPropagate", s.PropagateSkipBlock) + log.ErrFatal(err) + c.ProtocolRegister(skipchainBFT, func(n *sda.TreeNodeInstance) (sda.ProtocolInstance, error) { + return bftcosi.NewBFTCoSiProtocol(n, s.bftVerify) + }) if err := s.tryLoad(); err != nil { log.Error(err) } - for _, msg := range []interface{}{s.ProposeSkipBlock, s.SetChildrenSkipBlock, - s.GetUpdateChain} { - if err := s.RegisterMessage(msg); err != nil { - log.Fatal("Registration error for msg", msg, err) - } - } + log.ErrFatal(s.RegisterMessages(s.ProposeSkipBlock, s.SetChildrenSkipBlock, + s.GetUpdateChain)) if err := s.RegisterVerification(VerifyShard, s.VerifyShardFunc); err != nil { log.Panic(err) }