diff --git a/LICENSES/Agent15-LICENSE b/LICENSES/Agent15-LICENSE new file mode 100644 index 0000000..4bc4625 --- /dev/null +++ b/LICENSES/Agent15-LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/LICENSES/bouncy-castle-LICENSE b/LICENSES/bouncy-castle-LICENSE deleted file mode 100644 index d067d15..0000000 --- a/LICENSES/bouncy-castle-LICENSE +++ /dev/null @@ -1,8 +0,0 @@ -LICENSE -Copyright (c) 2000 - 2019 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/LICENSES/jackson-databind-LICENSE b/LICENSES/jackson-databind-LICENSE deleted file mode 100644 index 7a4a3ea..0000000 --- a/LICENSES/jackson-databind-LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. 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. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor 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 - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You 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 the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You 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 such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its 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. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/LICENSES/jackson-dataformat-cbor-LICENSE b/LICENSES/jackson-dataformat-cbor-LICENSE deleted file mode 100644 index 7a4a3ea..0000000 --- a/LICENSES/jackson-dataformat-cbor-LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. 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. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor 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 - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You 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 the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You 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 such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its 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. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/LICENSES/jackson-dataformat-msgpack-LICENSE b/LICENSES/jackson-dataformat-msgpack-LICENSE deleted file mode 100644 index 7a4a3ea..0000000 --- a/LICENSES/jackson-dataformat-msgpack-LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. 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. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor 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 - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You 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 the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You 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 such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its 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. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/LICENSES/pulpcore-LICENSE b/LICENSES/pulpcore-LICENSE deleted file mode 100644 index 517bd47..0000000 --- a/LICENSES/pulpcore-LICENSE +++ /dev/null @@ -1,26 +0,0 @@ -Copyright (c) 2007, Interactive Pulp, LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Interactive Pulp, LLC nor the names of its - contributors may be used to endorse or promote products derived from - this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/build.gradle b/build.gradle index b0102b8..b78d0b6 100644 --- a/build.gradle +++ b/build.gradle @@ -25,10 +25,15 @@ repositories { dependencies { - compileOnly 'org.projectlombok:lombok:1.18.20' - annotationProcessor 'org.projectlombok:lombok:1.18.20' + compileOnly 'org.projectlombok:lombok:1.18.22' + annotationProcessor 'org.projectlombok:lombok:1.18.22' + testCompileOnly 'org.projectlombok:lombok:1.18.22' + testAnnotationProcessor 'org.projectlombok:lombok:1.18.22' testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2' + testImplementation 'org.assertj:assertj-core:3.23.1' + testImplementation 'org.mockito:mockito-core:4.7.0' + testImplementation 'org.projectlombok:lombok:1.18.22' implementation 'com.google.guava:guava:24.1-jre' implementation 'commons-codec:commons-codec:1.6' @@ -50,13 +55,16 @@ dependencies { implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.0' implementation 'org.msgpack:jackson-dataformat-msgpack:0.8.18' implementation 'org.bouncycastle:bcpkix-jdk15on:1.64' - implementation 'org.eclipse.jetty.http2:http2-hpack:9.4.22.v20191022' implementation 'commons-net:commons-net:3.6' implementation 'org.nanohttpd:nanohttpd:2.3.1' implementation 'com.google.code.gson:gson:2.8.6' implementation 'org.apache.commons:commons-math3:3.0' implementation 'org.jfree:jfreechart:1.5.3' implementation 'org.ejml:ejml-all:0.41' + implementation 'at.favre.lib:hkdf:1.1.0' + implementation 'org.eclipse.jetty.http2:http2-hpack:11.0.11' + implementation 'org.eclipse.jetty.http3:http3-qpack:11.0.11' + implementation files('libs/agent15.jar') izpack 'org.codehaus.izpack:izpack-dist:5.1.3' diff --git a/libs/agent15.jar b/libs/agent15.jar new file mode 100644 index 0000000..26abd47 Binary files /dev/null and b/libs/agent15.jar differ diff --git a/src/main/java/core/packetproxy/PrivateDNSClient.java b/src/main/java/core/packetproxy/PrivateDNSClient.java index aa1dee0..97aea7b 100644 --- a/src/main/java/core/packetproxy/PrivateDNSClient.java +++ b/src/main/java/core/packetproxy/PrivateDNSClient.java @@ -1,10 +1,30 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package packetproxy; import org.xbill.DNS.Address; import org.xbill.DNS.SystemResolverConfig; +import packetproxy.util.PacketProxyUtility; import java.net.InetAddress; import java.net.UnknownHostException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; public class PrivateDNSClient { @@ -19,37 +39,60 @@ private static boolean isLocalAddress(String addr) throws UnknownHostException { return addr.equals(localAddr); } + private static boolean dnsLooping(String serverName) throws Exception { + return dnsLoopDetectedInDnsServer() || dnsLoopDetectedInEtcHosts(serverName); + } + // システムのDNS設定が、PacketProxyのDNSサーバが設定されているときtrueになる - private static boolean dnsLooping() { - try { - if (!PrivateDNS.getInstance().isRunning()) { - return false; - } - - // current system dns server setting - SystemResolverConfig systemResolver = new SystemResolverConfig(); - String dnsServer = systemResolver.server(); - - if (isLoopbackAddress(dnsServer)) { - return true; - } - if (isLocalAddress(dnsServer)) { - return true; - } + private static boolean dnsLoopDetectedInDnsServer() throws Exception { + if (!PrivateDNS.getInstance().isRunning()) { return false; + } + + // current system dns server setting + SystemResolverConfig systemResolver = new SystemResolverConfig(); + String dnsServer = systemResolver.server(); - } catch (Exception e) { - e.printStackTrace(); + if (isLoopbackAddress(dnsServer)) { + return true; + } + if (isLocalAddress(dnsServer)) { + return true; + } + return false; + } + + public static boolean dnsLoopDetectedInEtcHosts(String serverName) throws Exception { + if (PacketProxyUtility.getInstance().isMac() || PacketProxyUtility.getInstance().isUnix()) { + return dnsLoopingFromHostsLines(Files.readAllLines(Paths.get("/etc/hosts")), serverName); + } else { return false; } } - public static InetAddress getByName(String name) throws UnknownHostException { - return dnsLooping() ? Address.getByName(name) : InetAddress.getByName(name); + public static boolean dnsLoopingFromHostsLines(List fileLines, String serverName) { + return fileLines.stream() + .map(line -> line.contains("#") ? line.substring(0, line.indexOf("#")) : line) + .filter(line -> line.contains(serverName)) + .anyMatch(line -> { + try { + String addr = line.split(" ")[0]; + if (isLoopbackAddress(addr) || isLocalAddress(addr)) { + return true; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + }); + } + + public static InetAddress getByName(String serverName) throws Exception { + return dnsLooping(serverName) ? Address.getByName(serverName) : InetAddress.getByName(serverName); } - public static InetAddress[] getAllByName(String name) throws UnknownHostException { - return dnsLooping() ? Address.getAllByName(name) : InetAddress.getAllByName(name); + public static InetAddress[] getAllByName(String serverName) throws Exception { + return dnsLooping(serverName) ? Address.getAllByName(serverName) : InetAddress.getAllByName(serverName); } } diff --git a/src/main/java/core/packetproxy/ProxyFactory.java b/src/main/java/core/packetproxy/ProxyFactory.java index 8755237..c2e96ed 100644 --- a/src/main/java/core/packetproxy/ProxyFactory.java +++ b/src/main/java/core/packetproxy/ProxyFactory.java @@ -15,14 +15,12 @@ */ package packetproxy; -import java.net.ServerSocket; - import packetproxy.common.I18nString; -import packetproxy.http.Https; -import packetproxy.model.CAs.CA; import packetproxy.model.ListenPort; import packetproxy.util.PacketProxyUtility; +import java.net.ServerSocket; + public class ProxyFactory { public static Proxy create(ListenPort listen_info) throws Exception { @@ -49,6 +47,12 @@ public static Proxy create(ListenPort listen_info) throws Exception { } else if (listen_info.getType() == ListenPort.TYPE.UDP_FORWARDER) { proxy = new ProxyUDPForward(listen_info); + } else if (listen_info.getType() == ListenPort.TYPE.QUIC_FORWARDER) { + proxy = new ProxyQuicForward(listen_info); + + } else if (listen_info.getType() == ListenPort.TYPE.QUIC_TRANSPARENT_PROXY) { + proxy = new ProxyQuicTransparent(listen_info); + } else if (listen_info.getType() == ListenPort.TYPE.XMPP_SSL_FORWARDER) { PacketProxyUtility.getInstance().packetProxyLog("type is XMPP_SSL_FORWARDER"); ServerSocket listen_socket = new ServerSocket(listen_info.getPort()); diff --git a/src/main/java/core/packetproxy/ProxyHttpTransparent.java b/src/main/java/core/packetproxy/ProxyHttpTransparent.java index c2dae4f..4c1997a 100644 --- a/src/main/java/core/packetproxy/ProxyHttpTransparent.java +++ b/src/main/java/core/packetproxy/ProxyHttpTransparent.java @@ -1,5 +1,5 @@ /* - * Copyright 2019 DeNA Co., Ltd. + * Copyright 2022 DeNA Co., Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -65,7 +65,7 @@ public void run() { static class HostPort { String hostName; int port; - InetSocketAddress getInetSocketAddress() throws UnknownHostException { + InetSocketAddress getInetSocketAddress() throws Exception { return new InetSocketAddress(PrivateDNSClient.getByName(this.hostName), this.port); } } diff --git a/src/main/java/core/packetproxy/ProxyQuicForward.java b/src/main/java/core/packetproxy/ProxyQuicForward.java new file mode 100644 index 0000000..0f59280 --- /dev/null +++ b/src/main/java/core/packetproxy/ProxyQuicForward.java @@ -0,0 +1,66 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package packetproxy; + +import packetproxy.model.ListenPort; +import packetproxy.quic.service.connection.ClientConnection; +import packetproxy.quic.service.connection.ClientConnections; +import packetproxy.quic.service.connection.ServerConnection; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.util.PacketProxyUtility; + +public class ProxyQuicForward extends Proxy +{ + private ListenPort listen_info; + private ClientConnections clientConnections; + + public ProxyQuicForward(ListenPort listenInfo) throws Exception { + this.listen_info = listenInfo; + this.clientConnections = new ClientConnections(listenInfo.getPort(), listenInfo.getCA().get()); + } + + @Override + public void run() { + try { + while (true) { + ClientConnection clientConnection = this.clientConnections.accept(); + PacketProxyUtility.getInstance().packetProxyLog("accept"); + + String serverName = this.listen_info.getServer().getIp(); + PacketProxyUtility.getInstance().packetProxyLog(String.format("[QUIC-forward!] %s", serverName)); + + ServerConnection serverConnection = new ServerConnection( + ConnectionIdPair.generateRandom(), + serverName, + this.listen_info.getPort()); + + DuplexAsync duplex = DuplexFactory.createDuplexAsync( + clientConnection, + serverConnection, + this.listen_info.getServer().getEncoder()); + + duplex.start(); + DuplexManager.getInstance().registerDuplex(duplex); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void close() throws Exception { + this.clientConnections.close(); + } +} diff --git a/src/main/java/core/packetproxy/ProxyQuicTransparent.java b/src/main/java/core/packetproxy/ProxyQuicTransparent.java new file mode 100644 index 0000000..9e1eb07 --- /dev/null +++ b/src/main/java/core/packetproxy/ProxyQuicTransparent.java @@ -0,0 +1,73 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy; + +import packetproxy.model.ListenPort; +import packetproxy.model.Servers; +import packetproxy.quic.service.connection.ClientConnection; +import packetproxy.quic.service.connection.ClientConnections; +import packetproxy.quic.service.connection.ServerConnection; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.util.PacketProxyUtility; + +public class ProxyQuicTransparent extends Proxy +{ + private ListenPort listen_info; + private ClientConnections clientConnections; + + public ProxyQuicTransparent(ListenPort listenInfo) throws Exception { + this.listen_info = listenInfo; + this.clientConnections = new ClientConnections(listenInfo.getPort(), listenInfo.getCA().get()); + } + + @Override + public void run() { + try { + while (true) { + ClientConnection clientConnection = this.clientConnections.accept(); + PacketProxyUtility.getInstance().packetProxyLog("accept"); + + String sniServerName = clientConnection.getSNI(); + PacketProxyUtility.getInstance().packetProxyLog(String.format("[QUIC-forward! using SNI] %s", sniServerName)); + + ServerConnection serverConnection = new ServerConnection( + ConnectionIdPair.generateRandom(), + sniServerName, + this.listen_info.getPort()); + + String encoder = Servers.getInstance().queryByHostName(sniServerName).getEncoder(); + if (encoder == null) { + encoder = "Sample"; + } + + DuplexAsync duplex = DuplexFactory.createDuplexAsync( + clientConnection, + serverConnection, + encoder); + + duplex.start(); + DuplexManager.getInstance().registerDuplex(duplex); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void close() throws Exception { + this.clientConnections.close(); + } +} diff --git a/src/main/java/core/packetproxy/gui/GUIOptionListenPortDialog.java b/src/main/java/core/packetproxy/gui/GUIOptionListenPortDialog.java index df737b9..948415b 100644 --- a/src/main/java/core/packetproxy/gui/GUIOptionListenPortDialog.java +++ b/src/main/java/core/packetproxy/gui/GUIOptionListenPortDialog.java @@ -149,6 +149,15 @@ private void updateNextHopList(String item) throws Exception { JOptionPane.showMessageDialog(this, I18nString.get("Set server you wish to connect into 'Servers setting' first.")); dispose(); } + } else if (item.equals("QUIC_FORWARDER")) { + servers = Servers.getInstance().queryNonHttpProxies(); + if (servers.isEmpty()) { + JOptionPane.showMessageDialog(this, I18nString.get("Set server you wish to connect into 'Servers setting' first.")); + dispose(); + } + } else if (item.equals("QUIC_TRANSPARENT_PROXY")) { + servers = new ArrayList(); + combo.addItem(I18nString.get("Forward to server specified in SNI header")); } else if (item.equals("XMPP_SSL_FORWARDER")) { servers = Servers.getInstance().queryNonHttpProxies(); if (servers.isEmpty()) { @@ -174,9 +183,11 @@ private JComponent createTypeSetting() { type_combo.addItem("SSL_TRANSPARENT_PROXY"); type_combo.addItem("HTTP_TRANSPARENT_PROXY"); type_combo.addItem("UDP_FORWARDER"); + type_combo.addItem("QUIC_FORWARDER"); + type_combo.addItem("QUIC_TRANSPARENT_PROXY"); type_combo.addItem("XMPP_SSL_FORWARDER"); type_combo.setEnabled(true); - type_combo.setMaximumRowCount(6); + type_combo.setMaximumRowCount(9); type_combo.addItemListener(new ItemListener(){ @Override public void itemStateChanged(ItemEvent event) { @@ -246,6 +257,10 @@ public void actionPerformed(ActionEvent e) { type = ListenPort.TYPE.HTTP_TRANSPARENT_PROXY; } else if (type_combo.getSelectedItem().toString().equals("XMPP_SSL_FORWARDER")) { type = ListenPort.TYPE.XMPP_SSL_FORWARDER; + } else if (type_combo.getSelectedItem().toString().equals("QUIC_FORWARDER")) { + type = ListenPort.TYPE.QUIC_FORWARDER; + } else if (type_combo.getSelectedItem().toString().equals("QUIC_TRANSPARENT_PROXY")) { + type = ListenPort.TYPE.QUIC_TRANSPARENT_PROXY; } else { type = ListenPort.TYPE.SSL_FORWARDER; } diff --git a/src/main/java/core/packetproxy/gui/GUIOptionListenPorts.java b/src/main/java/core/packetproxy/gui/GUIOptionListenPorts.java index 82cb28e..5013aab 100644 --- a/src/main/java/core/packetproxy/gui/GUIOptionListenPorts.java +++ b/src/main/java/core/packetproxy/gui/GUIOptionListenPorts.java @@ -115,7 +115,7 @@ protected void addTableContent(ListenPort listenPort) { try { String serverNullStr = ""; TYPE type = listenPort.getType(); - if(type==TYPE.FORWARDER || type==TYPE.SSL_FORWARDER || type==TYPE.UDP_FORWARDER) + if(type==TYPE.FORWARDER || type==TYPE.SSL_FORWARDER || type==TYPE.UDP_FORWARDER || type==TYPE.QUIC_FORWARDER) serverNullStr = "Deleted"; option_model.addRow(new Object[] { listenPort.isEnabled(), diff --git a/src/main/java/core/packetproxy/http/Http.java b/src/main/java/core/packetproxy/http/Http.java index 43faff7..c43cc16 100644 --- a/src/main/java/core/packetproxy/http/Http.java +++ b/src/main/java/core/packetproxy/http/Http.java @@ -537,7 +537,7 @@ private static byte[] getChankedHttpBody(byte[] input_data) throws Exception return null; } - public InetSocketAddress getServerAddr() throws UnknownHostException { + public InetSocketAddress getServerAddr() throws Exception { return new InetSocketAddress(PrivateDNSClient.getByName(proxyHost), proxyPort); } diff --git a/src/main/java/core/packetproxy/http2/Grpc.java b/src/main/java/core/packetproxy/http2/Grpc.java index a8f67f9..8067bea 100644 --- a/src/main/java/core/packetproxy/http2/Grpc.java +++ b/src/main/java/core/packetproxy/http2/Grpc.java @@ -117,7 +117,7 @@ private byte[] encodeToFrames(byte[] data, HpackEncoder encoder) throws Exceptio int flags = Integer.valueOf(http.getFirstHeader("X-PacketProxy-HTTP2-Flags")); if (http.getBody() != null && http.getBody().length > 0) { http.updateHeader("X-PacketProxy-HTTP2-Flags", String.valueOf(flags & 0xff & ~HeadersFrame.FLAG_END_STREAM)); - HttpFields GRPC2ndHeaderHttpFields = new HttpFields(); + HttpFields.Mutable GRPC2ndHeaderHttpFields = HttpFields.build(); List unusedHeaders = new ArrayList<>(); for (HeaderField field : http.getHeader().getFields()) { diff --git a/src/main/java/core/packetproxy/http2/frames/HeadersFrame.java b/src/main/java/core/packetproxy/http2/frames/HeadersFrame.java index 9ca9b7a..d81f9ca 100644 --- a/src/main/java/core/packetproxy/http2/frames/HeadersFrame.java +++ b/src/main/java/core/packetproxy/http2/frames/HeadersFrame.java @@ -110,7 +110,7 @@ private void encodeFromHttp(HpackEncoder encoder, boolean originalHttpHeader) th version = HttpVersion.fromString("HTTP/2"); HttpHeader headers = (originalHttpHeader == true ? http.getOriginalHeader() : http.getHeader()); - fields = new HttpFields(); + HttpFields.Mutable mutableFields = HttpFields.build(); for (HeaderField field : headers.getFields()) { if (field.getName().equals("X-PacketProxy-HTTP2-Host")) { scheme = "https"; @@ -125,9 +125,10 @@ private void encodeFromHttp(HpackEncoder encoder, boolean originalHttpHeader) th } else if (field.getName().equals("X-PacketProxy-HTTP2-Weight")) { weight = Integer.parseInt(field.getValue()); } else { - fields.add(field.getName(), field.getValue()); + mutableFields.add(field.getName(), field.getValue()); } } + fields = mutableFields; MetaData meta; if (http.isRequest()) { @@ -136,9 +137,10 @@ private void encodeFromHttp(HpackEncoder encoder, boolean originalHttpHeader) th contentLength = (http.getBody().length == 0 ? Long.MIN_VALUE : http.getBody().length); } else if (method.equals("POST") || method.equals("PUT")) { contentLength = http.getBody().length; - fields.add("content-length", String.valueOf(contentLength)); + mutableFields.add("content-length", String.valueOf(contentLength)); + fields = mutableFields; } - HttpURI uri = new HttpURI(uriString); + HttpURI uri = HttpURI.build().uri(uriString); meta = new MetaData.Request(method, uri, version, fields, contentLength); } else { this.status = Integer.valueOf(http.getStatusCode()); diff --git a/src/main/java/core/packetproxy/model/ListenPort.java b/src/main/java/core/packetproxy/model/ListenPort.java index 2a70802..774bf51 100644 --- a/src/main/java/core/packetproxy/model/ListenPort.java +++ b/src/main/java/core/packetproxy/model/ListenPort.java @@ -23,7 +23,17 @@ @DatabaseTable(tableName = "listenports") public class ListenPort { - public enum TYPE { HTTP_PROXY, FORWARDER, SSL_FORWARDER, UDP_FORWARDER, SSL_TRANSPARENT_PROXY, HTTP_TRANSPARENT_PROXY, XMPP_SSL_FORWARDER }; + public enum TYPE { + HTTP_PROXY, + FORWARDER, + SSL_FORWARDER, + UDP_FORWARDER, + SSL_TRANSPARENT_PROXY, + HTTP_TRANSPARENT_PROXY, + XMPP_SSL_FORWARDER, + QUIC_FORWARDER, + QUIC_TRANSPARENT_PROXY + } @DatabaseField(generatedId = true) private int id; diff --git a/src/main/java/core/packetproxy/model/Server.java b/src/main/java/core/packetproxy/model/Server.java index 5bb699b..df126cb 100644 --- a/src/main/java/core/packetproxy/model/Server.java +++ b/src/main/java/core/packetproxy/model/Server.java @@ -79,7 +79,7 @@ static private boolean isHostName(String host){ public String toString() { return String.format("%s:%d(%s)", ip, port, encoder); } - public InetSocketAddress getAddress() throws UnknownHostException { + public InetSocketAddress getAddress() throws Exception { return new InetSocketAddress(PrivateDNSClient.getByName(ip), port); } public int getId() { @@ -147,7 +147,7 @@ public List getIps(){ ips.add(InetAddress.getByName(ip)); return ips; } - } catch (UnknownHostException e) { + } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return new ArrayList(); diff --git a/src/main/java/core/packetproxy/quic/service/LossDetection.java b/src/main/java/core/packetproxy/quic/service/LossDetection.java new file mode 100644 index 0000000..eec3b92 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/LossDetection.java @@ -0,0 +1,125 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service; + +import packetproxy.quic.service.connection.Connection; +import packetproxy.quic.service.pnspace.helper.LostPackets; +import packetproxy.quic.utils.ScheduledTimer; +import packetproxy.quic.value.frame.PingFrame; +import packetproxy.util.PacketProxyUtility; + +import java.time.Instant; + +import static packetproxy.quic.utils.Constants.PnSpaceType; +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceHandshake; +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceInitial; + +public class LossDetection { + + private final Connection conn; + private final ScheduledTimer scheduledTimer; + + public LossDetection(Connection conn) { + this.conn = conn; + this.scheduledTimer = new ScheduledTimer(this::onLossDetectionTimeout); + } + + public synchronized void setLossDetectionTimer() { + Instant earliestLossTime = this.conn.getPnSpaces().getEarliestLossTime(); + if (earliestLossTime != Instant.MAX) { + /* + * Time threshold loss detection. + * パケットロスが発生する時間にタイマーをセット + */ + scheduledTimer.update(earliestLossTime); + return; + } + if (this.conn.isServerAntiAmplifiedLimit()) { + /* + * The server's timer is not set if nothing can be sent. + * anti-amplification状態のときは、タイマーはセットしない。 + * ただ、PacketProxyでは、anti-amplificationは未実装なのでここに入らない。 + */ + scheduledTimer.cancel(); + return; + } + if (!this.conn.getPnSpaces().hasAnyAckElicitingPacket() && this.conn.peerCompletedAddressValidation()){ + /* + * There is nothing to detect lost, so no timer is set. + * However, the client needs to arm the timer if the + * server might be blocked by the anti-amplification limit. + * 何もパケットを送信していないし、anti-amplificationも解除されているので、タイマーをセットする必要がない + */ + scheduledTimer.cancel(); + return; + } + /* + * 最後にAckElicitingパケットを投げた時刻から計算したAckが返ってくる制限時間にタイマーをセット + */ + scheduledTimer.update(conn.getPto().getPtoTime()); + } + + private synchronized void onLossDetectionTimeout() { + // PacketProxyUtility.getInstance().packetProxyLogErr("[QUIC] LossDetection Timeout!"); + + var earliestLossTimeAndSpace = this.conn.getPnSpaces().getEarliestLossTimeAndSpace(); + Instant earliestLossTime = earliestLossTimeAndSpace.getLeft(); + PnSpaceType earliestPnSpaceType = earliestLossTimeAndSpace.getRight(); + + if (earliestLossTime != Instant.MAX) { + /* + * Time threshold loss Detection + * パケットロスが発生したので該当パケットを再送 + */ + LostPackets lostPackets = this.conn.getPnSpace(earliestPnSpaceType).detectAndRemoveLostPackets(); + assert (!lostPackets.isEmpty()); + this.conn.getPnSpace(earliestPnSpaceType).OnPacketsLost(lostPackets); + setLossDetectionTimer(); + return; + } + if (!this.conn.getPnSpaces().hasAnyAckElicitingPacket()) { + if (this.conn.peerAwaitingAddressValidation()) { + /* + * Client sends an anti-deadlock packet: Initial is padded to earn more anti-amplification credit, + * a Handshake packet proves address ownership. + * サーバーがanti-amplificationのため、何も送信できない状態に陥っているため、何かパケットを投げて送信できるようにする + */ + if (this.conn.getHandshakeState().hasHandshakeKeys()) { + this.conn.getPnSpace(PnSpaceHandshake).addSendFrame(new PingFrame()); + } else { + this.conn.getPnSpace(PnSpaceInitial).addSendFrame(new PingFrame()); + } + } + } else { + /* + * PTO. Send new data if available, else retransmit old data. + * If neither is available, send a single PING frame. + * AckElicitingパケットを投げたのに、Ackが返ってこないので催促する + */ + PnSpaceType ptoPnSpaceType = this.conn.getPto().getPtoSpaceType(); + PacketProxyUtility.getInstance().packetProxyLogErr( + String.format("[QUIC] Probe Timeout: send ack-eliciting packet (%s:%s)", + this.conn.getRole(), + ptoPnSpaceType.toString())); + + this.conn.getPnSpace(ptoPnSpaceType).addSendFrame(new PingFrame()); + } + this.conn.getPto().incrementPtoCount(); + setLossDetectionTimer(); + } + +} \ No newline at end of file diff --git a/src/main/java/core/packetproxy/quic/service/Pto.java b/src/main/java/core/packetproxy/quic/service/Pto.java new file mode 100644 index 0000000..193991c --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/Pto.java @@ -0,0 +1,98 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service; + + +import org.apache.commons.lang3.tuple.ImmutablePair; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.utils.Constants.*; +import packetproxy.quic.service.connection.Connection; + +import java.time.Instant; + +import static packetproxy.quic.utils.Constants.PnSpaceType.*; + + +public class Pto { + + private final Connection conn; + private long ptoCount = 0; + + public Pto(Connection conn) { + this.conn = conn; + } + + public void clearPtoCount() { + this.ptoCount = 0; + } + + public void incrementPtoCount() { + this.ptoCount++; + } + + public Instant getPtoTime() { + return this.getPtoTimeAndSpace().getLeft(); + } + + public PnSpaceType getPtoSpaceType() { + return this.getPtoTimeAndSpace().getRight(); + } + + public ImmutablePair getPtoTimeAndSpace() { + long smoothedRtt = this.conn.getRttEstimator().getSmoothedRtt(); + long rttVar = this.conn.getRttEstimator().getRttVar(); + + long duration = (smoothedRtt + Math.max(4 * rttVar, Constants.kGranularity)) * (long)(Math.pow(2, this.ptoCount)); + + // Anti-deadlock PTO starts from the current time + if (this.conn.peerAwaitingAddressValidation()) { + if (this.conn.getHandshakeState().hasNoHandshakeKeys()) { + return ImmutablePair.of( + Instant.now().plusMillis(duration), + PnSpaceInitial); + } else { + return ImmutablePair.of( + Instant.now().plusMillis(duration), + PnSpaceHandshake); + } + } + + Instant ptoTimeout = Instant.MAX; + PnSpaceType ptoPnSpaceType = PnSpaceInitial; + + for (PnSpaceType pnSpaceType : PnSpaceType.values()) { + if (!this.conn.getPnSpace(pnSpaceType).hasAnyAckElicitingPacket()) { + continue; + } + if (pnSpaceType == PnSpaceApplicationData) { + // Skip Application Data until handshake confirmed. + if (this.conn.getHandshakeState().isNotConfirmed()) { + return ImmutablePair.of(ptoTimeout, ptoPnSpaceType); + } + // Include max_ack_delay and backoff for Application Data. + duration += this.conn.getRttEstimator().getMaxAckDelay() * (long)(Math.pow(2, this.ptoCount)); + } + Instant t = this.conn.getPnSpace(pnSpaceType).getTimeOfLastAckElicitingPacket().plusMillis(duration); + if (t.isBefore(ptoTimeout)) { + ptoTimeout = t; + ptoPnSpaceType = pnSpaceType; + } + } + return ImmutablePair.of(ptoTimeout, ptoPnSpaceType); + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/RttEstimator.java b/src/main/java/core/packetproxy/quic/service/RttEstimator.java new file mode 100644 index 0000000..80668ee --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/RttEstimator.java @@ -0,0 +1,82 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service; + +import lombok.Getter; +import packetproxy.quic.service.connection.Connection; +import packetproxy.quic.utils.Constants; + +import java.time.Duration; +import java.time.Instant; + +import static java.lang.Math.min; + +@Getter +public class RttEstimator { + + private final Connection conn; + private long initialRtt = 333; + private Instant firstRttSample = Instant.MIN; + private long minRtt = 0; + private long smoothedRtt = initialRtt; + private long rttVar = initialRtt / 2; + private long latestRtt = 0; + private long maxAckDelay = 25; /* a default of 25 milliseconds is assumed (rfc9000) */ + + public RttEstimator(Connection conn) { + this.conn = conn; + } + + /** + * ref: https://www.rfc-editor.org/rfc/rfc9002.html#section-a.7 + */ + public void updateRtt(Instant timeSent, long ackDelay) { + + Instant timeReceived = Instant.now(); + this.latestRtt = Duration.between(timeReceived, timeSent).toMillis(); + + if (firstRttSample == Instant.MIN) { + this.minRtt = this.latestRtt; + this.smoothedRtt = this.latestRtt; + this.rttVar = this.latestRtt / 2; + this.firstRttSample = timeReceived; + } + + // min_rtt ignores acknowledgment delay. + this.minRtt = min(this.minRtt, this.latestRtt); + + // Limit ack_delay by max_ack_delay after handshake confirmation. + if (conn.getHandshakeState().isConfirmed()) { + ackDelay = min(ackDelay, this.maxAckDelay); + } + + // Adjust for acknowledgment delay if plausible. + long adjustedRtt = this.latestRtt; + if (this.latestRtt >= this.minRtt + ackDelay) { + adjustedRtt = this.latestRtt - ackDelay; + } + + long currentRttVar = Math.abs(this.smoothedRtt - adjustedRtt); + this.rttVar = (3 * this.rttVar + currentRttVar) / 4; + this.smoothedRtt = (7 * this.smoothedRtt + adjustedRtt) / 8; + } + + public long getLossDelay() { + // Minimum time of kGranularity before packets are deemed lost. + return (long)(Constants.kTimeThreshold * Math.max(this.latestRtt, this.smoothedRtt)); + } +} diff --git a/src/main/java/core/packetproxy/quic/service/connection/ClientConnection.java b/src/main/java/core/packetproxy/quic/service/connection/ClientConnection.java new file mode 100644 index 0000000..9b67d06 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/connection/ClientConnection.java @@ -0,0 +1,87 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.connection; + +import lombok.Getter; +import packetproxy.common.Endpoint; +import packetproxy.model.CAs.CA; +import packetproxy.quic.service.handshake.ServerHandshake; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.util.PacketProxyUtility; + +import javax.crypto.AEADBadTagException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; + +@Getter +public class ClientConnection extends Connection implements Endpoint { + + private final int listenPortNum; + + private final ServerHandshake handshake; + + public ClientConnection(ConnectionIdPair connIdPair, ConnectionId initialSecret, DatagramSocket socket, InetSocketAddress peerAddr, CA ca, int listenPortNum) throws Exception { + super(Constants.Role.SERVER, connIdPair, initialSecret, socket, peerAddr); + this.listenPortNum = listenPortNum; + this.handshake = new ServerHandshake(this, ca); + super.start(); + } + + /** + * ClientHello内部のSNIフィールドを取得する (Blocking) + */ + public String getSNI() throws Exception { + return this.handshake.getSNI(); + } + + /** + * クライアントからの受信パケット -> 処理 -> パケットキュー + */ + public void recvUdpPacket(DatagramPacket udpPacket) { + awaitingReceivedPackets.put(udpPacket); + awaitingReceivedPackets.forEachAndRemovedIfReturnTrue(packet -> { + try { + clientPacketParser.parseOnePacket(packet); + return true; + } catch (AEADBadTagException e) { + PacketProxyUtility.getInstance().packetProxyLogErr("[QUIC] Error: malformed packet received"); + return true; /* 読み飛ばす */ + } catch (Exception e) { + e.printStackTrace(); + return false; + } + }); + } + + @Override + public boolean peerCompletedAddressValidation() { + return true; + } + + @Override + public int getLocalPort() { + return this.listenPortNum; + } + + @Override + public String getName() { + return "QUIC Client Endpoint"; + } +} diff --git a/src/main/java/core/packetproxy/quic/service/connection/ClientConnections.java b/src/main/java/core/packetproxy/quic/service/connection/ClientConnections.java new file mode 100644 index 0000000..4ea50e0 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/connection/ClientConnections.java @@ -0,0 +1,106 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.connection; + +import lombok.Getter; +import org.apache.commons.lang3.ArrayUtils; +import packetproxy.model.CAs.CA; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.service.packet.QuicPacketParser; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@Getter +public class ClientConnections { + private final Map connes = new HashMap<>(); + private final List alreadyReceivedInitialSecrets = new ArrayList<>(); + private final ExecutorService executor = Executors.newFixedThreadPool(2); + private final int listenPort; + private final CA ca; + private DatagramSocket socket; + + + public ClientConnections(int listenPort, CA ca) { + this.listenPort = listenPort; + this.ca = ca; + try { + this.socket = new DatagramSocket(listenPort); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void close() { + if (!this.socket.isClosed()) { + this.connes.values().forEach(Connection::close); + this.executor.shutdownNow(); + this.socket.close(); + } + } + + /** + * Blocking until accepting UDP packet. + */ + public ClientConnection accept() throws Exception { + while (true) { + DatagramPacket udpPacket = recvUdpPacket(); + ConnectionId destConnId = QuicPacketParser.getDestConnectionId(udpPacket.getData()); + + if (find(destConnId).isPresent()) { + find(destConnId).get().recvUdpPacket(udpPacket); + } else { + Optional conn = this.create(destConnId, new InetSocketAddress(udpPacket.getAddress(), udpPacket.getPort())); + if (conn.isPresent()) { + conn.get().recvUdpPacket(udpPacket); + return conn.get(); + } + } + } + } + + public Optional find(ConnectionId destConnId) { + ClientConnection conn = this.connes.get(destConnId); + return (conn != null) ? Optional.of(conn) : Optional.empty(); + } + + public Optional create(ConnectionId initialSecret, InetSocketAddress peerAddr) throws Exception { + if (alreadyReceivedInitialSecrets.contains(initialSecret)) { + /* 以前受信したことのある initialSecret を受信した。再送を意味するので無視 */ + return Optional.empty(); + } + alreadyReceivedInitialSecrets.add(initialSecret); + ConnectionIdPair connIdPair = ConnectionIdPair.generateRandom(); + ClientConnection conn = new ClientConnection(connIdPair, initialSecret, this.socket, peerAddr, this.ca, this.listenPort); + connes.put(connIdPair.getSrcConnId(), conn); + return Optional.of(conn); + } + + private DatagramPacket recvUdpPacket() throws Exception { + byte[] buf = new byte[4096]; + DatagramPacket udpPacket = new DatagramPacket(buf, 4096); + this.socket.receive(udpPacket); + udpPacket.setData(ArrayUtils.subarray(udpPacket.getData(), 0, udpPacket.getLength())); + return udpPacket; + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/connection/Connection.java b/src/main/java/core/packetproxy/quic/service/connection/Connection.java new file mode 100644 index 0000000..817e626 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/connection/Connection.java @@ -0,0 +1,251 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.connection; + +import lombok.Getter; +import packetproxy.common.Endpoint; +import packetproxy.common.PipeEndpoint; +import packetproxy.quic.service.LossDetection; +import packetproxy.quic.service.Pto; +import packetproxy.quic.service.RttEstimator; +import packetproxy.quic.service.connection.helper.AwaitingPackets; +import packetproxy.quic.service.handshake.Handshake; +import packetproxy.quic.service.handshake.HandshakeState; +import packetproxy.quic.service.key.Keys; +import packetproxy.quic.service.packet.QuicPacketParser; +import packetproxy.quic.service.pnspace.PnSpace; +import packetproxy.quic.service.pnspace.PnSpaces; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.utils.Constants.PnSpaceType; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.value.QuicMessage; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.packet.QuicPacket; +import packetproxy.quic.value.packet.longheader.pnspace.HandshakePacket; +import packetproxy.quic.value.packet.longheader.pnspace.InitialPacket; +import packetproxy.quic.value.packet.shortheader.ShortHeaderPacket; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.InterruptedIOException; +import java.io.OutputStream; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static packetproxy.util.Throwing.rethrow; + +@Getter +public abstract class Connection implements Endpoint { + final ExecutorService executor = Executors.newFixedThreadPool(3); + final QuicPacketParser clientPacketParser; + final QuicPacketParser serverPacketParser; + final AwaitingPackets awaitingSendPackets = new AwaitingPackets(); + final AwaitingPackets awaitingReceivedPackets = new AwaitingPackets(); + final HandshakeState handshakeState = new HandshakeState(); + final Keys keys = new Keys(); + final Constants.Role role; + final DatagramSocket socket; + final Pto pto; + final LossDetection lossDetection; + final RttEstimator rttEstimator; + final PnSpaces pnSpaces; + final PipeEndpoint pipe; + final InetSocketAddress peerAddr; + ConnectionIdPair connIdPair; + ConnectionId initialSecret; + boolean serverAntiAmplifiedLimit = false; + + public Connection(Constants.Role role, ConnectionIdPair connIdPair, ConnectionId initialSecret, DatagramSocket socket, InetSocketAddress peerAddr) throws Exception { + this.role = role; + this.connIdPair = connIdPair; + this.socket = socket; + this.peerAddr = peerAddr; + this.clientPacketParser = new QuicPacketParser(this, keys.getClientKeys()); + this.serverPacketParser = new QuicPacketParser(this, keys.getServerKeys()); + this.pto = new Pto(this); + this.lossDetection = new LossDetection(this); + this.rttEstimator = new RttEstimator(this); + this.pnSpaces = new PnSpaces(this); + this.pipe = new PipeEndpoint(peerAddr); + this.connIdPair = connIdPair; + this.initialSecret = initialSecret; + this.keys.computeInitialKey(initialSecret); + } + + protected void start() throws Exception { + /* 送信パケットキュー -> 送信 */ + this.executor.submit(new Runnable() { + @Override + public void run() { + while (true) { + List packets = pnSpaces.pollSendPackets(); // Blocking here + awaitingSendPackets.put(packets); + awaitingSendPackets.forEachAndRemovedIfReturnTrue(packet -> { + try { + //if (role == Constants.Role.SERVER) { + // PacketProxyUtility.getInstance().packetProxyLog("[QUIC] CLIENT<--- " + packet); + //} else { + // PacketProxyUtility.getInstance().packetProxyLog("[QUIC] --->SERVER " + packet); + //} + if (packet instanceof InitialPacket) { + InitialPacket ip = (InitialPacket) packet; + if (keys.discardedInitialKey()) { + return true; /* Initialキーは破棄済みなのでInitialパケットは送信せずに破棄する */ + } + byte[] udpData = (role == Constants.Role.CLIENT) ? + ip.getBytes(keys.getClientKeys().getInitialKey(), getPnSpace(ip.getPnSpaceType()).getLargestAckedPn()): + ip.getBytes(keys.getServerKeys().getInitialKey(), getPnSpace(ip.getPnSpaceType()).getLargestAckedPn()); + sendUdpPacket(udpData); + return true; + } + if (packet instanceof HandshakePacket) { + HandshakePacket hp = (HandshakePacket) packet; + if (keys.discardedHandshakeKey()) { + return true; /* Handshakeキーは破棄済みなのでHandshakeパケットは送信せずに破棄する */ + } + byte[] udpData = (role == Constants.Role.CLIENT) ? + hp.getBytes(keys.getClientKeys().getHandshakeKey(), getPnSpace(hp.getPnSpaceType()).getLargestAckedPn()): + hp.getBytes(keys.getServerKeys().getHandshakeKey(), getPnSpace(hp.getPnSpaceType()).getLargestAckedPn()); + sendUdpPacket(udpData); + return true; + } + if (packet instanceof ShortHeaderPacket && keys.hasApplicationKey()) { + if (role == Constants.Role.CLIENT && getHandshakeState().isNotConfirmed()) { + return false; /* Handshakeが終わってからShortHeaderPacketを送信しないとサーバー側でエラーになってしまうので、後ほど送信 */ + } + ShortHeaderPacket sp = (ShortHeaderPacket) packet; + byte[] udpData = (role == Constants.Role.CLIENT) ? + sp.getBytes(keys.getClientKeys().getApplicationKey(), getPnSpace(sp.getPnSpaceType()).getLargestAckedPn()): + sp.getBytes(keys.getServerKeys().getApplicationKey(), getPnSpace(sp.getPnSpaceType()).getLargestAckedPn()); + sendUdpPacket(udpData); + return true; + } + } catch (Exception e) { + e.printStackTrace(); + } + return false; + }); + } + } + }); + + /* エンコーダー -> 送信パケットキュー */ + this.executor.submit(new Runnable() { + @Override + public void run() { + try { + InputStream in = pipe.getRawEndpoint().getInputStream(); + + /* + * ハンドシェイクが完了するまで待ってからエンコーダーからの入力をパケット化する + * ハンドシェイクが完了してApplicationKeyを得ていないとパケット化できないため + */ + if (role == Constants.Role.CLIENT) { + while (true) { + if (handshakeState.isConfirmed()) { + break; + } + Thread.sleep(100); + } + } + + byte[] readChunk = new byte[4096]; + ByteArrayOutputStream readQueue = new ByteArrayOutputStream(); + int length; + while ((length = in.read(readChunk)) > 0) { + readQueue.write(readChunk, 0, length); + ByteBuffer buffer = ByteBuffer.wrap(readQueue.toByteArray()); + QuicMessage.parse(buffer).forEach(rethrow(msg -> { + getPnSpace(Constants.PnSpaceType.PnSpaceApplicationData).addSendQuicMessage(msg); + })); + readQueue.reset(); + if (buffer.hasRemaining()) { + readQueue.write(SimpleBytes.parse(buffer, buffer.remaining()).getBytes()); + } + } + } catch (InterruptedIOException e) { + /* exception simply ignored */ + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + private void sendUdpPacket(byte[] data) throws Exception { + DatagramPacket udpPacket = new DatagramPacket(data, 0, data.length, this.peerAddr); + socket.send(udpPacket); + } + + public void updateDestConnId(ConnectionId destConnId) { + this.connIdPair = ConnectionIdPair.of(this.connIdPair.getSrcConnId(), destConnId); + } + + public PnSpace getPnSpace(PnSpaceType pnSpaceType) { + return this.pnSpaces.getPnSpace(pnSpaceType); + } + + public void close() { + try { + this.pipe.getRawEndpoint().getInputStream().close(); + this.pipe.getRawEndpoint().getOutputStream().close(); + this.executor.shutdownNow(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public InputStream getInputStream() throws Exception { + return this.pipe.getProxyRawEndpoint().getInputStream(); + } + + @Override + public OutputStream getOutputStream() throws Exception { + return this.pipe.getProxyRawEndpoint().getOutputStream(); + } + + @Override + public InetSocketAddress getAddress() { + return this.peerAddr; + } + + @Override + public int getLocalPort() { + return 0; + } + + @Override + public String getName() { + return "QUIC Endpoint"; + } + + public boolean peerAwaitingAddressValidation() { + return !this.peerCompletedAddressValidation(); + } + + public abstract boolean peerCompletedAddressValidation(); + + public abstract Handshake getHandshake(); + +} diff --git a/src/main/java/core/packetproxy/quic/service/connection/ServerConnection.java b/src/main/java/core/packetproxy/quic/service/connection/ServerConnection.java new file mode 100644 index 0000000..0a36cd4 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/connection/ServerConnection.java @@ -0,0 +1,92 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.connection; + +import lombok.Getter; +import lombok.SneakyThrows; +import org.apache.commons.lang3.ArrayUtils; +import packetproxy.PrivateDNSClient; +import packetproxy.quic.service.handshake.ClientHandshake; +import packetproxy.quic.utils.AwaitingException; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.ConnectionIdPair; + +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.InetSocketAddress; + +@Getter +public class ServerConnection extends Connection { + + private final ClientHandshake handshake; + + public ServerConnection(ConnectionIdPair connIdPair, String serverName, int serverPort) throws Exception { + super(Constants.Role.CLIENT, + connIdPair, + connIdPair.getDestConnId(), + new DatagramSocket(), + new InetSocketAddress(PrivateDNSClient.getByName(serverName), serverPort)); + this.handshake = new ClientHandshake(this); + this.handshake.start(serverName); + super.executor.submit(new RecvUdpPacketsLoop()); + super.start(); + } + + /* サーバから受信 -> 処理 -> SendPacketキュー */ + public class RecvUdpPacketsLoop implements Runnable { + @Override + @SneakyThrows + public void run() { + while (true) { + DatagramPacket udpPacket = recvUdpPacket(); // Blocking here + awaitingReceivedPackets.put(udpPacket); + awaitingReceivedPackets.forEachAndRemovedIfReturnTrue(packet -> { + try { + serverPacketParser.parseOnePacket(packet); + return true; + } catch (AwaitingException e) { + return false; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + }); + } + } + } + + private DatagramPacket recvUdpPacket() throws Exception { + byte[] buf = new byte[4096]; + DatagramPacket recvPacket = new DatagramPacket(buf, 4096); + socket.receive(recvPacket); + recvPacket.setData(ArrayUtils.subarray(recvPacket.getData(), 0, recvPacket.getLength())); + return recvPacket; + } + + @Override + public boolean peerCompletedAddressValidation() { + // Servers complete address validation when a protected packet is received. + return this.handshakeState.isAckReceived(); + } + + @Override + public String getName() { + return "QUIC Server Endpoint"; + } + +} + diff --git a/src/main/java/core/packetproxy/quic/service/connection/helper/AwaitingPackets.java b/src/main/java/core/packetproxy/quic/service/connection/helper/AwaitingPackets.java new file mode 100644 index 0000000..abec1bf --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/connection/helper/AwaitingPackets.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.connection.helper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedDeque; +import java.util.function.Predicate; + +public class AwaitingPackets { + + private final Queue awaitings = new ConcurrentLinkedDeque<>(); + + public synchronized void put(List packets) { + awaitings.addAll(packets); + } + + public synchronized void put(T packet) { + this.awaitings.offer(packet); + } + + public synchronized T get() { + return this.awaitings.poll(); + } + + /** + * 全ての要素に対して関数を適用する。関数の結果がtrueになった場合は要素から削除 + */ + public synchronized void forEachAndRemovedIfReturnTrue(Predicate predicate) { + T e; + List targets = new ArrayList<>(); + while ((e = awaitings.poll()) != null) { + targets.add(e); + } + List failed = new ArrayList<>(); + targets.forEach(target -> { + if (!predicate.test(target)) { + failed.add(target); + } + }); + if (!failed.isEmpty()) { + this.awaitings.addAll(failed); + } + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/frame/FrameParser.java b/src/main/java/core/packetproxy/quic/service/frame/FrameParser.java new file mode 100644 index 0000000..f27350c --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/frame/FrameParser.java @@ -0,0 +1,82 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.frame; + +import com.google.common.collect.Sets; +import lombok.SneakyThrows; +import packetproxy.quic.value.frame.Frame; +import packetproxy.quic.value.frame.UnknownFrame; + +import javax.tools.*; +import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class FrameParser { + + private static final String framePackage = "packetproxy.quic.value.frame"; + private static final Class frameClass = Frame.class; + private static Map> frameMap; + + @SneakyThrows + private static void createFrameMap() { + frameMap = new HashMap<>(); + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + JavaFileManager fm = compiler.getStandardFileManager(new DiagnosticCollector(), null, null); + + Set kind = Sets.newHashSet(JavaFileObject.Kind.CLASS); + for (JavaFileObject f : fm.list(StandardLocation.CLASS_PATH, framePackage, kind, true)) { + Path encode_file_path = Paths.get(f.getName()); + String encode_class_path = encode_file_path.toString() + .replaceAll("/",".") + .replaceFirst("^.*" + framePackage, framePackage) + .replaceAll("\\.class.*$", ""); + Class klass = Class.forName(encode_class_path); + if(frameClass.isAssignableFrom(klass) && !Modifier.isAbstract(klass.getModifiers())){ + List types = (List) klass.getMethod("supportedTypes").invoke(null); + types.forEach(type -> frameMap.put(type, (Class)klass)); + } + } + } + + static private Frame createInstance(Class klass, ByteBuffer buffer) throws Exception { + return (Frame) klass.getMethod("parse", ByteBuffer.class).invoke(null, buffer); + } + + static public Frame create(ByteBuffer buffer) throws Exception { + int saved = buffer.position(); + byte type = buffer.get(); + buffer.position(saved); + + if (frameMap == null) { + createFrameMap(); + } + + Class klass = frameMap.get(type); + if (klass == null) { + throw new Exception(String.format("Error: unknown frame type: %x", type)); + } else { + return createInstance(klass, buffer); + } + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/frame/Frames.java b/src/main/java/core/packetproxy/quic/service/frame/Frames.java new file mode 100644 index 0000000..084a549 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/frame/Frames.java @@ -0,0 +1,97 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.frame; + +import com.google.common.collect.ImmutableList; +import lombok.Value; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.value.frame.Frame; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +@Value +public class Frames implements Iterable { + + static public final Frames empty = new Frames(ImmutableList.of()); + + static public Frames parse(byte[] bytes) { + return Frames.parse(ByteBuffer.wrap(bytes)); + } + + static public Frames parse(ByteBuffer buffer) { + try { + List frames = new ArrayList<>(); + while (buffer.remaining() > 0) { + Frame frame = FrameParser.create(buffer); + frames.add(frame); + } + return new Frames(frames); + } catch (Exception e) { + e.printStackTrace(); + } + return Frames.empty; + } + + static public Frames of(Frame e1) { + return new Frames(ImmutableList.of(e1)); + } + + static public Frames of(Frame e1, Frame e2) { + return new Frames(ImmutableList.of(e1, e2)); + } + + static public Frames of(Frame e1, Frame e2, Frame e3) { + return new Frames(ImmutableList.of(e1, e2, e3)); + } + + static public Frames of(List frameList) { + return new Frames(new ArrayList<>(frameList)); + } + + List frames; + + public boolean isAckEliciting() { + return frames.stream().anyMatch(Frame::isAckEliciting); + } + + public boolean hasAckFrame() { + return frames.stream().anyMatch(frame -> frame instanceof AckFrame); + } + + public Optional getAckFrame() { + return frames.stream() + .filter(f -> f instanceof AckFrame) + .map(f -> (AckFrame)f) + .findFirst(); + } + + public String toString() { + return String.format("Frames(%s)", + frames.stream().map(Object::toString).collect(Collectors.joining(","))); + } + + @Override + public Iterator iterator() { + return frames.iterator(); + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/frame/FramesBuilder.java b/src/main/java/core/packetproxy/quic/service/frame/FramesBuilder.java new file mode 100644 index 0000000..17693af --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/frame/FramesBuilder.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.frame; + +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.frame.Frame; +import packetproxy.quic.value.frame.PaddingFrame; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class FramesBuilder { + List frames = new ArrayList<>(); + + public FramesBuilder add(Frame frame) { + frames.add(frame); + return this; + } + + /* + * Clients MUST ensure that UDP datagrams containing Initial packets have + * UDP payloads of at least 1200 bytes, adding PADDING frames as necessary. + */ + public FramesBuilder addPaddingFramesToEnsure1200Bytes() { + long currentBytesLength = getBytes().length; + long paddingFrameLength = 1200 - currentBytesLength; + if (paddingFrameLength > 0) { + this.add(new PaddingFrame(paddingFrameLength)); + } + return this; + } + + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + for (Frame frame: frames) { + buffer.put(frame.getBytes()); + } + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + public Frames build() { + return new Frames(frames); + } +} diff --git a/src/main/java/core/packetproxy/quic/service/framegenerator/AckFrameGenerator.java b/src/main/java/core/packetproxy/quic/service/framegenerator/AckFrameGenerator.java new file mode 100644 index 0000000..b2b1ed5 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/framegenerator/AckFrameGenerator.java @@ -0,0 +1,120 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator; + +import packetproxy.quic.service.framegenerator.helper.ReceivedPacketNumbers; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.value.frame.helper.AckRange; +import packetproxy.quic.value.frame.helper.AckRanges; +import packetproxy.quic.value.PacketNumber; + +import java.util.ArrayList; +import java.util.List; + +public class AckFrameGenerator { + private long largestAckedPn = -1; + private long smallestValidPn = 0; + private ReceivedPacketNumbers receivedPacketNumbers = new ReceivedPacketNumbers(); + + public void received(PacketNumber packetNumber) { + this.received(packetNumber.getNumber()); + } + + public void received(long receivedPn) { + if (receivedPn < this.smallestValidPn) { + return; + } + if (this.largestAckedPn < receivedPn) { + for (long i = this.largestAckedPn +1; i < receivedPn; i++) { + this.receivedPacketNumbers.unreceived(i); + } + this.receivedPacketNumbers.received(receivedPn); + this.largestAckedPn = receivedPn; + } + else if (receivedPn < this.largestAckedPn) { + this.receivedPacketNumbers.received(receivedPn); + } + } + + public void confirmedAckFrame(AckFrame ackFrameConfirmedByPeer) { + this.smallestValidPn = ackFrameConfirmedByPeer.getLargestAcknowledged()+1; + receivedPacketNumbers.clearLessThan(this.smallestValidPn); + if (largestAckedPn == ackFrameConfirmedByPeer.getLargestAcknowledged()) { + largestAckedPn = -1; + } + } + + private boolean ackRangeExists(long packetNumber) { + // 最低でも2個分スペースがないと、AckRangeを生成できない + return packetNumber >= this.smallestValidPn + 2; + } + + public AckFrame generateAckFrame() { + if (largestAckedPn == -1) { + return null; + } + long smallestOfRange = this.receivedPacketNumbers.getSmallestOfRange(this.largestAckedPn, this.smallestValidPn); + long firstAckRange = this.largestAckedPn - smallestOfRange; + if (!ackRangeExists(smallestOfRange)) { + return new AckFrame(this.largestAckedPn, 0, 0, firstAckRange, AckRanges.emptyAckRanges); + } + AckRanges ackRanges = generateAckRanges(smallestOfRange); + + return new AckFrame(this.largestAckedPn, 0, ackRanges.size(), firstAckRange, ackRanges); + } + + public PacketNumber getLargestAckedPn() { + return this.largestAckedPn == -1 ? + PacketNumber.Infinite : + PacketNumber.of(this.largestAckedPn); + } + + public AckRanges generateAckRanges(long smallestOfRange) { + List ackRanges = new ArrayList<>(); + while (ackRangeExists(smallestOfRange)) { + long largestOfGap = smallestOfRange - 1; + AckRange ackRange = generateAckRange(largestOfGap); + if (ackRange == null) { + break; + } + ackRanges.add(ackRange); + smallestOfRange -= ackRange.size(); + } + return new AckRanges(ackRanges); + } + + public AckRange generateAckRange(long largestOfGap) { + if (this.receivedPacketNumbers.isReceived(largestOfGap)) { + System.err.println(String.format("largestGap(%d) is not a gap", largestOfGap)); + return null; + } + + long smallestOfGap = this.receivedPacketNumbers.getSmallestOfGap(largestOfGap, this.smallestValidPn); + long gap = largestOfGap - smallestOfGap; + + if (smallestOfGap == this.smallestValidPn) { + return null; + } + + long largestOfRange = smallestOfGap - 1; + long smallestOfRange = this.receivedPacketNumbers.getSmallestOfRange(largestOfRange, this.smallestValidPn); + long range = largestOfRange - smallestOfRange; + + return new AckRange(gap, range); + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/framegenerator/CryptoFramesToMessages.java b/src/main/java/core/packetproxy/quic/service/framegenerator/CryptoFramesToMessages.java new file mode 100644 index 0000000..34caa60 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/framegenerator/CryptoFramesToMessages.java @@ -0,0 +1,139 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator; + +import lombok.SneakyThrows; +import net.luminis.tls.ProtectionKeysType; +import net.luminis.tls.TlsProtocolException; +import net.luminis.tls.handshake.*; +import org.apache.commons.lang3.ArrayUtils; +import packetproxy.quic.value.frame.CryptoFrame; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +public class CryptoFramesToMessages { + + static public HandshakeMessage convertToHandshakeMessage(byte[] handshakeMsgBytes) throws Exception { + TlsMessageParser parser = new TlsMessageParser(); + DebugMessageProcessor processor = new DebugMessageProcessor(); + return parser.parseAndProcessHandshakeMessage(ByteBuffer.wrap(handshakeMsgBytes), processor, ProtectionKeysType.None); + } + + private final List cryptoFrames = new ArrayList<>(); + private final ByteArrayOutputStream messages = new ByteArrayOutputStream(); + private int nextMessageLength; + private int alreadyParsedBytes; + + + public CryptoFramesToMessages() { + this.nextMessageLength = 0; + this.alreadyParsedBytes = 0; + } + + public void write(CryptoFrame cryptoFrame) { + cryptoFrames.add(cryptoFrame); + } + + public List getHandshakeMessages() { + List messages = new ArrayList<>(); + for (var msgOpt = getHandshakeMessage(); msgOpt.isPresent(); msgOpt = getHandshakeMessage()) { + messages.add(msgOpt.get()); + } + return messages; + } + + @SneakyThrows + public Optional getHandshakeMessage() { + + if (this.nextMessageLength == 0) { + if (this.messages.size() < 4) { /* header(4B) = messageType(1B) + messageLength(3B) */ + if (!refillOneCryptoFrameToNextMessageBuffer(this.alreadyParsedBytes + this.messages.size())) { + return Optional.empty(); + } + } + byte[] data = this.messages.toByteArray(); + this.nextMessageLength = 4; /* header(4B) */ + this.nextMessageLength += ((data[1] & 0xff) << 16) | ((data[2] & 0xff) << 8) | (data[3] & 0xff); + } + + while (this.messages.size() < this.nextMessageLength) { + if (!refillOneCryptoFrameToNextMessageBuffer(this.alreadyParsedBytes + this.messages.size())) { + return Optional.empty(); + } + } + + byte[] messagesBytes = this.messages.toByteArray(); + byte[] message = ArrayUtils.subarray(messagesBytes, 0, this.nextMessageLength); + byte[] remaining = ArrayUtils.subarray(messagesBytes, this.nextMessageLength, messagesBytes.length); + this.messages.reset(); + this.messages.write(remaining); /* 読み過ぎた分は書き戻す */ + this.nextMessageLength = 0; + this.alreadyParsedBytes += message.length; + + TlsMessageParser parser = new TlsMessageParser(); + DebugMessageProcessor processor = new DebugMessageProcessor(); + HandshakeMessage handshakeMessage = parser.parseAndProcessHandshakeMessage(ByteBuffer.wrap(message), processor, ProtectionKeysType.None); + return Optional.of(handshakeMessage); + } + + private Optional getCryptoFrameByOffset(long requiredOffset) { + return cryptoFrames.stream().filter(frame -> frame.getOffset() == requiredOffset).findFirst(); + } + + @SneakyThrows + private boolean refillOneCryptoFrameToNextMessageBuffer(long offset) { + Optional cryptoFrameOptional = getCryptoFrameByOffset(offset); + if (cryptoFrameOptional.isPresent()) { + this.messages.write(cryptoFrameOptional.get().getData()); + return true; + } + return false; + } + + static private class DebugMessageProcessor implements MessageProcessor { + @Override + public void received(ClientHello ch, ProtectionKeysType protectedBy) throws TlsProtocolException, IOException { + } + @Override + public void received(ServerHello sh, ProtectionKeysType protectedBy) throws TlsProtocolException, IOException { + } + @Override + public void received(EncryptedExtensions ee, ProtectionKeysType protectedBy) throws TlsProtocolException, IOException { + } + @Override + public void received(CertificateMessage cm, ProtectionKeysType protectedBy) throws TlsProtocolException, IOException { + } + @Override + public void received(CertificateVerifyMessage cv, ProtectionKeysType protectedBy) throws TlsProtocolException, IOException { + } + @Override + public void received(FinishedMessage fm, ProtectionKeysType protectedBy) throws TlsProtocolException, IOException { + } + @Override + public void received(NewSessionTicketMessage nst, ProtectionKeysType protectedBy) throws TlsProtocolException, IOException { + } + @Override + public void received(CertificateRequestMessage cr, ProtectionKeysType protectedBy) throws TlsProtocolException, IOException { + } + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/framegenerator/MessagesToCryptoFrames.java b/src/main/java/core/packetproxy/quic/service/framegenerator/MessagesToCryptoFrames.java new file mode 100644 index 0000000..21507f1 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/framegenerator/MessagesToCryptoFrames.java @@ -0,0 +1,58 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator; + +import net.luminis.tls.handshake.HandshakeMessage; +import org.apache.commons.lang3.ArrayUtils; +import packetproxy.quic.value.frame.CryptoFrame; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.service.frame.FramesBuilder; + +import java.util.ArrayList; +import java.util.List; + +public class MessagesToCryptoFrames { + private final List handshakeMessages = new ArrayList<>(); + private long offset = 0; + + public synchronized void write(HandshakeMessage handshakeMessage) { + this.handshakeMessages.add(handshakeMessage); + } + + + public synchronized Frames toCryptoFrames() { + FramesBuilder framesBuilder = new FramesBuilder(); + + for (HandshakeMessage msg : handshakeMessages) { + byte[] msgBytes = msg.getBytes(); + int msgLen = msgBytes.length; + int msgOff = 0; + while (msgLen > 0) { + int subMsgLen = Math.min(msgLen, 1200); + byte[] subMsg = ArrayUtils.subarray(msgBytes, msgOff, msgOff+subMsgLen); + CryptoFrame cryptoFrame = new CryptoFrame(this.offset, subMsg); + framesBuilder.add(cryptoFrame); + this.offset += subMsgLen; + msgOff += subMsgLen; + msgLen -= subMsgLen; + } + } + this.handshakeMessages.clear(); /* remove all messages */ + return framesBuilder.build(); + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/framegenerator/MessagesToStreamFrames.java b/src/main/java/core/packetproxy/quic/service/framegenerator/MessagesToStreamFrames.java new file mode 100644 index 0000000..7022e8f --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/framegenerator/MessagesToStreamFrames.java @@ -0,0 +1,81 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator; + +import org.apache.commons.lang3.ArrayUtils; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.value.QuicMessage; +import packetproxy.quic.value.frame.Frame; +import packetproxy.quic.value.frame.StreamFrame; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MessagesToStreamFrames { + + private final List frameList = new ArrayList<>(); + private final Map continuousStreamMap = new HashMap<>(); + + public synchronized void put(QuicMessage msg) { + long streamId = msg.getStreamId(); + + if ((streamId & 0x02) == 0x00) { /* bi-directional stream */ + byte[] data = msg.getData(); + int remaining = data.length; + int subOffset = 0; + while (remaining > 0) { + int subLength = Math.min(remaining, 1200); + byte[] subData = ArrayUtils.subarray(data, subOffset, subOffset + subLength); + boolean finishFlag = (subOffset + subLength == data.length); + this.frameList.add(StreamFrame.of(streamId, subOffset, subLength, subData, finishFlag)); + remaining -= subLength; + subOffset += subLength; + } + + } else { /* uni-directional stream */ + Long offsetObj = this.continuousStreamMap.get(streamId); + long offset = 0; + if (offsetObj != null) { + offset = offsetObj; + } + byte[] data = msg.getData(); + int remaining = data.length; + int subOffset = 0; + while (remaining > 0) { + int subLength = Math.min(remaining, 1200); + byte[] subData = ArrayUtils.subarray(data, subOffset, subOffset + subLength); + this.frameList.add(StreamFrame.of(streamId, offset, subLength, subData, false)); + remaining -= subLength; + subOffset += subLength; + offset += subLength; + } + this.continuousStreamMap.put(streamId, offset); + } + } + + /** + * get and remove all StreamFrames + */ + public synchronized Frames get() { + Frames frames = Frames.of(this.frameList); + this.frameList.clear(); + return frames; + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/framegenerator/StreamFramesToMessages.java b/src/main/java/core/packetproxy/quic/service/framegenerator/StreamFramesToMessages.java new file mode 100644 index 0000000..2fc281e --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/framegenerator/StreamFramesToMessages.java @@ -0,0 +1,83 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator; + +import lombok.SneakyThrows; +import packetproxy.quic.service.framegenerator.helper.ContinuousStream; +import packetproxy.quic.service.framegenerator.helper.OneshotStream; +import packetproxy.quic.value.frame.StreamFrame; +import packetproxy.quic.value.QuicMessage; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class StreamFramesToMessages { + + private final Map continuousStreamMap = new HashMap<>(); + private final Map oneshotStreamMap = new HashMap<>(); + + public void put(StreamFrame frame) { + long streamId = frame.getStreamId(); + if ((streamId & 0x02) == 0x00) { /* bi-directional */ + this.putToOneshot(frame); + } else { /* uni-directional */ + this.putToContinuous(frame); + } + } + + private void putToContinuous(StreamFrame frame) { + long streamId = frame.getStreamId(); + if (!continuousStreamMap.containsKey(streamId)) { + continuousStreamMap.put(streamId, new ContinuousStream(streamId)); + } + this.continuousStreamMap.get(streamId).put(frame); + } + + private void putToOneshot(StreamFrame frame) { + long streamId = frame.getStreamId(); + if (!oneshotStreamMap.containsKey(streamId)) { + oneshotStreamMap.put(streamId, new OneshotStream(streamId)); + } + this.oneshotStreamMap.get(streamId).put(frame); + } + + public Optional get(long streamId) { + if ((streamId & 0x02) == 0x00) { /* bi-directional */ + return this.getFromOneshot(streamId); + } else { /* uni-directional */ + return this.getFromContinuous(streamId); + } + } + + @SneakyThrows + private Optional getFromContinuous(long streamId) { + if (!this.continuousStreamMap.containsKey(streamId)) { + return Optional.empty(); + } + return this.continuousStreamMap.get(streamId).get(); + } + + @SneakyThrows + private Optional getFromOneshot(long streamId) { + if (!this.oneshotStreamMap.containsKey(streamId)) { + return Optional.empty(); + } + return this.oneshotStreamMap.get(streamId).get(); + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/framegenerator/helper/ContinuousStream.java b/src/main/java/core/packetproxy/quic/service/framegenerator/helper/ContinuousStream.java new file mode 100644 index 0000000..f6d6d00 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/framegenerator/helper/ContinuousStream.java @@ -0,0 +1,65 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator.helper; + +import lombok.Getter; +import packetproxy.quic.value.QuicMessage; +import packetproxy.quic.value.frame.StreamFrame; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@Getter +public class ContinuousStream { + private final Map frameMap = new HashMap<>(); + private final long streamId; + private long currentOffset = 0; + private boolean finished = false; + + public ContinuousStream(long streamId) { + this.streamId = streamId; + } + + public void put(StreamFrame frame) { + if (frame.isFinished()) { + this.finished = true; + } + this.frameMap.put(frame.getOffset(), frame); + } + + public Optional get() throws Exception { + ByteArrayOutputStream data = new ByteArrayOutputStream(); + + StreamFrame frame = this.frameMap.get(this.currentOffset); + if (frame == null) { + return Optional.empty(); + } + data.write(frame.getStreamData()); + this.currentOffset += frame.getLength(); + + /* process continuous frame if they exist */ + while (this.frameMap.get(this.currentOffset) != null) { + StreamFrame extraFrame = this.frameMap.get(this.currentOffset); + data.write(extraFrame.getBytes()); + this.currentOffset += extraFrame.getLength(); + } + + return Optional.of(QuicMessage.of(this.streamId, data.toByteArray())); + } +} diff --git a/src/main/java/core/packetproxy/quic/service/framegenerator/helper/OneshotStream.java b/src/main/java/core/packetproxy/quic/service/framegenerator/helper/OneshotStream.java new file mode 100644 index 0000000..1198601 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/framegenerator/helper/OneshotStream.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator.helper; + +import packetproxy.quic.value.frame.StreamFrame; +import packetproxy.quic.value.QuicMessage; + +import java.io.ByteArrayOutputStream; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class OneshotStream { + private final Map frameMap = new HashMap<>(); + private final long streamId; + private boolean lastFrameReceived = false; + private long totalLength = 0; + + public OneshotStream(long streamId) { + this.streamId = streamId; + } + + public void put(StreamFrame frame) { + if (frame.isFinished()) { + this.lastFrameReceived = true; + this.totalLength = frame.getOffset() + frame.getLength(); + } + this.frameMap.put(frame.getOffset(), frame); + } + + public Optional get() throws Exception { + if (!this.lastFrameReceived) { + return Optional.empty(); + } + long offset = 0; + ByteArrayOutputStream data = new ByteArrayOutputStream(); + while (offset < this.totalLength) { + StreamFrame frame = this.frameMap.get(offset); + if (frame == null) { + return Optional.empty(); + } + offset += frame.getLength(); + data.write(frame.getStreamData()); + } + return Optional.of(QuicMessage.of(this.streamId, data.toByteArray())); + } +} diff --git a/src/main/java/core/packetproxy/quic/service/framegenerator/helper/ReceivedPacketNumbers.java b/src/main/java/core/packetproxy/quic/service/framegenerator/helper/ReceivedPacketNumbers.java new file mode 100644 index 0000000..553b0ba --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/framegenerator/helper/ReceivedPacketNumbers.java @@ -0,0 +1,85 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator.helper; + +import packetproxy.util.PacketProxyUtility; + +import java.util.TreeSet; + +public class ReceivedPacketNumbers { + private TreeSet unreceivedPacketNumbers = new TreeSet<>(); + + public void unreceived(long packetNumber) { + unreceivedPacketNumbers.add(packetNumber); + } + + public void received(long packetNumber) { + unreceivedPacketNumbers.remove(packetNumber); + } + + public boolean isReceived(long packetNumber) { + return !unreceivedPacketNumbers.contains(packetNumber); + } + + public boolean isUnreceived(long packetNumber) { + return unreceivedPacketNumbers.contains(packetNumber); + } + + public long getSmallestOfRange(long largestOfRange, long smallestValid) { + assert(smallestValid <= largestOfRange); + if (unreceivedPacketNumbers.contains(largestOfRange)) { + PacketProxyUtility.getInstance().packetProxyLogErr(String.format("[QUIC] Error: AckRange: %d isn't in ack_range", largestOfRange)); + return 0; + } + return getSmallestReceived(largestOfRange, smallestValid); + } + + public long getSmallestOfGap(long largestOfGap, long smallestValid) { + assert(smallestValid <= largestOfGap); + if (!unreceivedPacketNumbers.contains(largestOfGap)) { + PacketProxyUtility.getInstance().packetProxyLogErr(String.format("[QUIC] Error: AckRange: %d isn't in gap", largestOfGap)); + return 0; + } + return getSmallestUnreceived(largestOfGap, smallestValid); + } + + public void clearLessThan(long packetNumber) { + unreceivedPacketNumbers.removeIf(pn -> pn < packetNumber); + } + + private long getSmallestUnreceived(long largestOfGap, long smallestValid) { + assert(smallestValid <= largestOfGap); + for (long i = largestOfGap; i >= smallestValid; i--) { + if (!unreceivedPacketNumbers.contains(i)) { + return i+1; + } + } + return smallestValid; + } + + private long getSmallestReceived(long largestOfRange, long smallestValid) { + assert(smallestValid <= largestOfRange); + if (unreceivedPacketNumbers.isEmpty()) { // 全部受信済み + return smallestValid; + } + if (largestOfRange < unreceivedPacketNumbers.first()) { // 全部受信済み + return smallestValid; + } + return Math.max(smallestValid, unreceivedPacketNumbers.floor(largestOfRange)+1); + } + +} \ No newline at end of file diff --git a/src/main/java/core/packetproxy/quic/service/handshake/ClientHandshake.java b/src/main/java/core/packetproxy/quic/service/handshake/ClientHandshake.java new file mode 100644 index 0000000..c3d7ba5 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/handshake/ClientHandshake.java @@ -0,0 +1,161 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.handshake; + +import net.luminis.tls.*; +import net.luminis.tls.extension.ApplicationLayerProtocolNegotiationExtension; +import net.luminis.tls.extension.Extension; +import net.luminis.tls.handshake.*; +import packetproxy.quic.service.connection.Connection; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.service.framegenerator.MessagesToCryptoFrames; +import packetproxy.quic.service.transportparameter.TransportParameters; +import packetproxy.quic.utils.Constants; + +import javax.net.ssl.X509TrustManager; +import java.io.IOException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.List; + +import static packetproxy.quic.service.handshake.HandshakeState.State.*; +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceHandshake; +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceInitial; + +public class ClientHandshake implements Handshake { + + private final Connection conn; + private final TlsClientEngine engine; + + public ClientHandshake(Connection conn) { + this.conn = conn; + this.engine = new TlsClientEngine(new MyClientMessageSender(), new MyClientTlsStatusEventHandler()); + this.engine.addSupportedCiphers(List.of(TlsConstants.CipherSuite.TLS_AES_128_GCM_SHA256)); + this.engine.add(new ApplicationLayerProtocolNegotiationExtension("h3")); + this.engine.setTrustManager(new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + }); + this.engine.setHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String s, X509Certificate x509Certificate) { + return true; + } + }); + TransportParameters tp = new TransportParameters(Constants.Role.CLIENT); + tp.setInitSrcConnId(this.conn.getConnIdPair().getSrcConnId().getBytes()); + tp.setMaxUdpPayloadSize(1472); + tp.setAckDelayExponent(10); + tp.setMaxIdleTimeout(30_000); + tp.setOldMinAckDelay(25_000); + tp.setInitMaxStreamUni(10 * 1024); + tp.setInitMaxStreamBidi(10 * 1024); + tp.setInitMaxData(10L * 1024 * 1024 * 1024); + tp.setInitMaxStreamDataBidiLocal(10L * 1024 * 1024 * 1024); + tp.setInitMaxStreamDataBidiRemote(10L * 1024 * 1024 * 1024); + tp.setInitMaxStreamDataUni(10L * 1024 * 1024 * 1024); + tp.setActiveConnIdLimit(4); + this.engine.add(tp); + } + + @Override + public void received(Message message) throws Exception { + if (message instanceof ServerHello) { + this.engine.received((ServerHello) message, ProtectionKeysType.None); + } else if (message instanceof EncryptedExtensions) { + this.engine.received((EncryptedExtensions) message, ProtectionKeysType.Handshake); + } else if (message instanceof CertificateMessage) { + this.engine.received((CertificateMessage) message, ProtectionKeysType.Handshake); + } else if (message instanceof CertificateVerifyMessage) { + this.engine.received((CertificateVerifyMessage) message, ProtectionKeysType.Handshake); + } else if (message instanceof FinishedMessage) { + this.engine.received((FinishedMessage) message, ProtectionKeysType.Handshake); + } else if (message instanceof NewSessionTicketMessage) { + this.engine.received((NewSessionTicketMessage) message, ProtectionKeysType.Application); + } else { + System.err.println("Error: couldn't process message " + message); + } + } + + public void start(String serverName) throws Exception { + this.engine.setServerName(serverName); + this.engine.startHandshake(); + } + + class MyClientMessageSender implements ClientMessageSender { + @Override + public void send(ClientHello clientHello) throws IOException { + MessagesToCryptoFrames stream = conn.getPnSpace(PnSpaceInitial).getMsgToFrameCryptoStream(); + stream.write(clientHello); + Frames frames = stream.toCryptoFrames(); + conn.getPnSpace(PnSpaceInitial).addSendFrames(frames); + conn.getKeys().setClientRandom(clientHello.getClientRandom()); + } + @Override + public void send(FinishedMessage finishedMessage) throws IOException { + MessagesToCryptoFrames stream = conn.getPnSpace(PnSpaceHandshake).getMsgToFrameCryptoStream(); + stream.write(finishedMessage); + Frames frames = stream.toCryptoFrames(); + conn.getPnSpace(PnSpaceHandshake).addSendFrames(frames); + conn.getHandshakeState().transit(Confirmed); + } + @Override + public void send(CertificateMessage certificateMessage) throws IOException { + /* do nothing */ + } + @Override + public void send(CertificateVerifyMessage certificateVerifyMessage) { + /* do nothing */ + } + } + + class MyClientTlsStatusEventHandler implements TlsStatusEventHandler { + @Override + public void earlySecretsKnown() { + conn.getKeys().computeZeroRttKey(engine.getClientEarlyTrafficSecret()); + } + @Override + public void handshakeSecretsKnown() { + conn.getKeys().computeHandshakeKey(engine.getClientHandshakeTrafficSecret(), engine.getServerHandshakeTrafficSecret()); + conn.getHandshakeState().transit(HasHandshakeKeys); + } + @Override + public void handshakeFinished() { + conn.getKeys().computeApplicationKey(engine.getClientApplicationTrafficSecret(), engine.getServerApplicationTrafficSecret()); + conn.getHandshakeState().transit(HasAppKeys); + } + @Override + public void newSessionTicketReceived(NewSessionTicket ticket) { + } + @Override + public void extensionsReceived(List extensions) throws TlsProtocolException { + } + @Override + public boolean isEarlyDataAccepted() { + return false; + } + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/handshake/Handshake.java b/src/main/java/core/packetproxy/quic/service/handshake/Handshake.java new file mode 100644 index 0000000..a84b81f --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/handshake/Handshake.java @@ -0,0 +1,25 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.handshake; + +import net.luminis.tls.Message; + +public interface Handshake { + + void received(Message message) throws Exception; + +} diff --git a/src/main/java/core/packetproxy/quic/service/handshake/HandshakeState.java b/src/main/java/core/packetproxy/quic/service/handshake/HandshakeState.java new file mode 100644 index 0000000..38ac1ce --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/handshake/HandshakeState.java @@ -0,0 +1,59 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.handshake; + +public class HandshakeState { + + public enum State { + Initial, + HasHandshakeKeys, + AckReceived, + HasAppKeys, + Confirmed + } + + private State state; + + public HandshakeState() { + this.state = State.Initial; + } + + public void transit(HandshakeState.State newlyState) { + this.state = newlyState; + } + + public boolean hasNoHandshakeKeys() { + return this.state.ordinal() < State.HasHandshakeKeys.ordinal(); + } + + public boolean hasHandshakeKeys() { + return this.state.ordinal() >= State.HasHandshakeKeys.ordinal(); + } + + public boolean isAckReceived() { + return this.state.ordinal() >= State.AckReceived.ordinal(); + } + + public boolean isNotConfirmed() { + return this.state.ordinal() < State.Confirmed.ordinal(); + } + + public boolean isConfirmed() { + return this.state.ordinal() >= State.Confirmed.ordinal(); + } + +} \ No newline at end of file diff --git a/src/main/java/core/packetproxy/quic/service/handshake/ServerHandshake.java b/src/main/java/core/packetproxy/quic/service/handshake/ServerHandshake.java new file mode 100644 index 0000000..1f1c984 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/handshake/ServerHandshake.java @@ -0,0 +1,219 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.handshake; + +import net.luminis.tls.*; +import net.luminis.tls.extension.ApplicationLayerProtocolNegotiationExtension; +import net.luminis.tls.extension.Extension; +import net.luminis.tls.extension.ServerNameExtension; +import net.luminis.tls.handshake.*; +import packetproxy.CertCacheManager; +import packetproxy.model.CAs.CA; +import packetproxy.quic.service.connection.Connection; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.service.framegenerator.MessagesToCryptoFrames; +import packetproxy.quic.service.transportparameter.TransportParameters; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.Token; +import packetproxy.quic.value.frame.NewConnectionIdFrame; + +import java.io.IOException; +import java.security.KeyStore; +import java.security.cert.X509Certificate; +import java.security.interfaces.RSAPrivateKey; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.stream.Collectors; + +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceHandshake; +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceInitial; +import static packetproxy.quic.utils.Constants.TOKEN_SIZE; +import static packetproxy.util.Throwing.rethrow; + +public class ServerHandshake implements Handshake { + + static private final TlsSessionRegistry tlsSessionRegistry = new TlsSessionRegistryImpl(); + + final Connection conn; + final BlockingQueue sniQueue = new LinkedBlockingQueue<>(); + final CA ca; + Optional sniName = Optional.empty(); + TlsServerEngine engine; + + public ServerHandshake(Connection conn, CA ca) { + this.conn = conn; + this.ca = ca; + } + + public void startHandshake(String sniName) throws Exception { + KeyStore ks = CertCacheManager.getInstance().getKeyStore(sniName, new String[]{sniName}, this.ca); + RSAPrivateKey key = (RSAPrivateKey) ks.getKey("newalias", "testtest".toCharArray()); + List certs = Arrays.stream(ks.getCertificateChain("newalias")) + .map(cert -> (X509Certificate)cert) + .collect(Collectors.toList()); + this.engine = new TlsServerEngine(certs, key, new MyServerMessageSender(), new MyTlsStatusEventHandler(), tlsSessionRegistry); + this.engine.addSupportedCiphers(List.of(TlsConstants.CipherSuite.TLS_AES_128_GCM_SHA256)); + } + + @Override + public void received(Message message) throws Exception { + if (message instanceof ClientHello) { + /* SNIを取得 */ + ClientHello ch = (ClientHello) message; + ch.getExtensions().stream() + .filter(ext -> ext instanceof ServerNameExtension) + .findFirst() + .ifPresent(rethrow(ext -> { + ServerNameExtension serverNameExtension = (ServerNameExtension) ext; + sniName = Optional.of(serverNameExtension.getHostName()); + })); + if (sniName.isEmpty()) { + throw new Exception("Error: SNI name was not found in TLS ClientHello HandShake message"); + } + sniName.ifPresent(rethrow(sni -> { + this.startHandshake(sni); + this.sniQueue.put(sni); + this.engine.received((ClientHello) message, ProtectionKeysType.None); + })); + + } else if (message instanceof EncryptedExtensions) { + /* do nothing */ + } else if (message instanceof FinishedMessage) { + this.engine.received((FinishedMessage) message, ProtectionKeysType.Handshake); + } else { + System.err.println("Error: couldn't process message " + message); + } + } + + public String getSNI() throws Exception { + return this.sniQueue.take(); + } + + class MyServerMessageSender implements ServerMessageSender { + @Override + public void send(ServerHello serverHello) throws IOException { + MessagesToCryptoFrames stream = conn.getPnSpace(Constants.PnSpaceType.PnSpaceInitial).getMsgToFrameCryptoStream(); + stream.write(serverHello); + Frames frames = stream.toCryptoFrames(); + conn.getPnSpace(Constants.PnSpaceType.PnSpaceInitial).addSendFrames(frames); + conn.getKeys().setClientRandom(serverHello.getRandom()); + } + @Override + public void send(EncryptedExtensions encryptedExtensions) throws IOException { + MessagesToCryptoFrames stream = conn.getPnSpace(Constants.PnSpaceType.PnSpaceHandshake).getMsgToFrameCryptoStream(); + stream.write(encryptedExtensions); + Frames frames = stream.toCryptoFrames(); + conn.getPnSpace(Constants.PnSpaceType.PnSpaceHandshake).addSendFrames(frames); + } + @Override + public void send(CertificateMessage certificateMessage) throws IOException { + MessagesToCryptoFrames stream = conn.getPnSpace(Constants.PnSpaceType.PnSpaceHandshake).getMsgToFrameCryptoStream(); + stream.write(certificateMessage); + Frames frames = stream.toCryptoFrames(); + conn.getPnSpace(Constants.PnSpaceType.PnSpaceHandshake).addSendFrames(frames); + } + @Override + public void send(CertificateVerifyMessage certificateVerifyMessage) throws IOException { + MessagesToCryptoFrames stream = conn.getPnSpace(Constants.PnSpaceType.PnSpaceHandshake).getMsgToFrameCryptoStream(); + stream.write(certificateVerifyMessage); + Frames frames = stream.toCryptoFrames(); + conn.getPnSpace(Constants.PnSpaceType.PnSpaceHandshake).addSendFrames(frames); + } + @Override + public void send(FinishedMessage finishedMessage) throws IOException { + MessagesToCryptoFrames stream = conn.getPnSpace(Constants.PnSpaceType.PnSpaceHandshake).getMsgToFrameCryptoStream(); + stream.write(finishedMessage); + Frames frames = stream.toCryptoFrames(); + conn.getPnSpace(Constants.PnSpaceType.PnSpaceHandshake).addSendFrames(frames); + } + @Override + public void send(NewSessionTicketMessage ticket) throws IOException { + /* send CryptFrame(NewSessionTicketMassage) */ + MessagesToCryptoFrames stream = conn.getPnSpace(Constants.PnSpaceType.PnSpaceApplicationData).getMsgToFrameCryptoStream(); + stream.write(ticket); + Frames frames = stream.toCryptoFrames(); + conn.getPnSpace(Constants.PnSpaceType.PnSpaceApplicationData).addSendFrames(frames); + + /* send NewConnectionIdFrame */ + NewConnectionIdFrame frame1 = new NewConnectionIdFrame(1, 0, ConnectionId.generateRandom(), Token.generateRandom(TOKEN_SIZE)); + NewConnectionIdFrame frame2 = new NewConnectionIdFrame(2, 0, ConnectionId.generateRandom(), Token.generateRandom(TOKEN_SIZE)); + NewConnectionIdFrame frame3 = new NewConnectionIdFrame(3, 0, ConnectionId.generateRandom(), Token.generateRandom(TOKEN_SIZE)); + conn.getPnSpace(Constants.PnSpaceType.PnSpaceApplicationData).addSendFrames(Frames.of(frame1, frame2, frame3)); + } + } + + class MyTlsStatusEventHandler implements TlsStatusEventHandler { + @Override + public void earlySecretsKnown() { + //System.out.println("earlySecretsKnown"); + conn.getKeys().computeZeroRttKey(engine.getClientEarlyTrafficSecret()); + } + @Override + public void handshakeSecretsKnown() { + //System.out.println("handshakeSecretsKnown"); + conn.getKeys().computeHandshakeKey(engine.getClientHandshakeTrafficSecret(), engine.getServerHandshakeTrafficSecret()); + } + @Override + public void handshakeFinished() { + //System.out.println("handshakeFinished"); + conn.getKeys().computeApplicationKey(engine.getClientApplicationTrafficSecret(), engine.getServerApplicationTrafficSecret()); + conn.getKeys().discardInitialKey(); + conn.getPnSpace(PnSpaceInitial).close(); + conn.getKeys().discardHandshakeKey(); + conn.getPnSpace(PnSpaceHandshake).close(); + } + @Override + public void newSessionTicketReceived(NewSessionTicket ticket) { + //System.out.println("newSessionTicketReceived"); + } + @Override + public void extensionsReceived(List extensions) throws TlsProtocolException { + /* use always h3 mode */ + engine.addServerExtensions(new ApplicationLayerProtocolNegotiationExtension("h3")); + + /* quic transport parameter */ + TransportParameters tp = new TransportParameters(Constants.Role.SERVER); + tp.setMaxUdpPayloadSize(1472); + tp.setAckDelayExponent(10); + tp.setMaxIdleTimeout(30_000); + tp.setOldMinAckDelay(25000); + tp.setOrigDestConnId(conn.getInitialSecret().getBytes()); + tp.setInitSrcConnId(conn.getConnIdPair().getSrcConnId().getBytes()); + tp.setInitMaxStreamUni(10 * 1024); + tp.setInitMaxStreamBidi(10 * 1024); + tp.setInitMaxData(10L * 1024 * 1024 * 1024); + tp.setInitMaxStreamDataBidiLocal(10L * 1024 * 1024 * 1024); + tp.setInitMaxStreamDataBidiRemote(10L * 1024 * 1024 * 1024); + tp.setInitMaxStreamDataUni(10L * 1024 * 1024 * 1024); + tp.setActiveConnIdLimit(4); + tp.setDisableActiveMigration(true); + //if (retryRequired) { + // tp.setRetrySrcConnId(); + //} + engine.addServerExtensions(tp); + } + @Override + public boolean isEarlyDataAccepted() { + //System.out.println("isEarlyDataAccepted"); + return false; + } + } +} diff --git a/src/main/java/core/packetproxy/quic/service/key/Keys.java b/src/main/java/core/packetproxy/quic/service/key/Keys.java new file mode 100644 index 0000000..3c3b519 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/key/Keys.java @@ -0,0 +1,104 @@ +package packetproxy.quic.service.key; + +import lombok.Getter; +import org.apache.commons.codec.binary.Hex; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.utils.Constants; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Getter +public class Keys { + + private final RoleKeys clientKeys = new RoleKeys(Constants.Role.CLIENT); + private final RoleKeys serverKeys = new RoleKeys(Constants.Role.SERVER); + private byte[] clientRandom; + + public void computeInitialKey(ConnectionId destConnId) { + this.clientKeys.computeInitialKey(destConnId); + this.serverKeys.computeInitialKey(destConnId); + } + + public void computeZeroRttKey(byte[] secret) { + this.clientKeys.computeZeroRttKey(secret); + } + + public void computeHandshakeKey(byte[] clientSecret, byte[] serverSecret) { + this.clientKeys.computeHandshakeKey(clientSecret); + this.serverKeys.computeHandshakeKey(serverSecret); + } + + public void computeApplicationKey(byte[] clientSecret, byte[] serverSecret) { + this.clientKeys.computeApplicationKey(clientSecret); + this.serverKeys.computeApplicationKey(serverSecret); + outputSecretsForWireshark(); /* for wireshark debug */ + } + + public boolean hasInitialKey() { + return this.clientKeys.hasInitialKey(); + } + + public boolean hasHandshakeKey() { + return this.clientKeys.hasHandshakeKey(); + } + + public boolean hasApplicationKey() { + return this.clientKeys.hasApplicationKey(); + } + + public RoleKeys getRoleKeys(Constants.Role role) { + return (role == Constants.Role.CLIENT) ? this.clientKeys : this.serverKeys; + } + + public void setClientRandom(byte[] clientRandom) { + this.clientRandom = clientRandom; + } + + public void discardInitialKey() { + this.clientKeys.discardInitialKey(); + this.serverKeys.discardInitialKey(); + } + + public void discardHandshakeKey() { + this.clientKeys.discardHandshakeKey(); + this.serverKeys.discardHandshakeKey(); + } + + public boolean discardedInitialKey() { + return this.clientKeys.discardedInitialKey(); + } + + public boolean discardedHandshakeKey() { + return this.clientKeys.discardedHandshakeKey(); + } + + static private Path logDir = Paths.get(System.getProperty("user.home") + "/.packetproxy/logs"); + static private Path keylogFile = Paths.get(logDir + "/quic_tls.keylog"); + + /** + * Wiresharkのメニューから、Preference > Advanced > tls.keylog_file に keylog ファイルのパスを入力した後、 + * View > Reload で再読み込みするとQUICパケットのペイロードが読めるようになる + */ + public void outputSecretsForWireshark() { + if (this.clientRandom != null && this.clientKeys.hasHandshakeKey() && this.clientKeys.hasApplicationKey()) { + try { + if (!Files.exists(logDir)) { + Files.createDirectories(logDir); + } + try (FileWriter file = new FileWriter(keylogFile.toFile())) { + String clientRandomStr = Hex.encodeHexString(this.clientRandom); + file.write(String.format("CLIENT_HANDSHAKE_TRAFFIC_SECRET %s %s\n", clientRandomStr, Hex.encodeHexString(this.clientKeys.getHandshakeKey().getSecret()))); + file.write(String.format("SERVER_HANDSHAKE_TRAFFIC_SECRET %s %s\n", clientRandomStr, Hex.encodeHexString(this.serverKeys.getHandshakeKey().getSecret()))); + file.write(String.format("CLIENT_TRAFFIC_SECRET_0 %s %s\n", clientRandomStr, Hex.encodeHexString(this.clientKeys.getApplicationKey().getSecret()))); + file.write(String.format("SERVER_TRAFFIC_SECRET_0 %s %s\n", clientRandomStr, Hex.encodeHexString(this.serverKeys.getApplicationKey().getSecret()))); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/key/RoleKeys.java b/src/main/java/core/packetproxy/quic/service/key/RoleKeys.java new file mode 100644 index 0000000..9e26dbe --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/key/RoleKeys.java @@ -0,0 +1,102 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.key; + +import lombok.Getter; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.key.level.ZeroRttKey; +import packetproxy.quic.value.key.level.ApplicationKey; +import packetproxy.quic.value.key.level.HandshakeKey; +import packetproxy.quic.value.key.level.InitialKey; +import packetproxy.quic.value.ConnectionId; + +import java.util.Optional; + +@Getter +public class RoleKeys { + + private Constants.Role role; + + private Optional optionalInitialKey = Optional.empty(); + private Optional optionalZeroRttKey = Optional.empty(); + private Optional optionalHandshakeKey = Optional.empty(); + private Optional optionalApplicationKey = Optional.empty(); + + boolean discardedInitialKey = false; + boolean discardedHandshakeKey = false; + + public RoleKeys(Constants.Role role) { + this.role = role; + } + + public InitialKey getInitialKey() { + return optionalInitialKey.orElseThrow(); + } + public HandshakeKey getHandshakeKey() { + return this.optionalHandshakeKey.orElseThrow(); + } + public ZeroRttKey getZeroRttKey() { + return this.optionalZeroRttKey.orElseThrow(); + } + public ApplicationKey getApplicationKey() { + return this.optionalApplicationKey.orElseThrow(); + } + + public boolean hasInitialKey() { + return this.optionalInitialKey.isPresent(); + } + public boolean hasHandshakeKey() { + return this.optionalHandshakeKey.isPresent(); + } + public boolean hasZeroRttKey() { + return this.optionalZeroRttKey.isPresent(); + } + public boolean hasApplicationKey() { + return this.optionalApplicationKey.isPresent(); + } + + public void computeInitialKey(ConnectionId destConnId) { + this.optionalInitialKey = Optional.of(InitialKey.of(this.role, destConnId)); + } + public void computeZeroRttKey(byte[] secret) { + this.optionalZeroRttKey = Optional.of(ZeroRttKey.of(secret)); + } + public void computeHandshakeKey(byte[] secret) { + this.optionalHandshakeKey = Optional.of(HandshakeKey.of(secret)); + } + public void computeApplicationKey(byte[] secret) { + this.optionalApplicationKey = Optional.of(ApplicationKey.of(secret)); + } + + public void discardInitialKey() { + this.optionalInitialKey = Optional.empty(); + this.discardedInitialKey = true; + } + + public void discardHandshakeKey() { + this.optionalHandshakeKey = Optional.empty(); + this.discardedHandshakeKey = true; + } + + public boolean discardedInitialKey() { + return discardedInitialKey; + } + + public boolean discardedHandshakeKey() { + return discardedHandshakeKey; + } +} diff --git a/src/main/java/core/packetproxy/quic/service/packet/QuicPacketBuilder.java b/src/main/java/core/packetproxy/quic/service/packet/QuicPacketBuilder.java new file mode 100644 index 0000000..a6aa890 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/packet/QuicPacketBuilder.java @@ -0,0 +1,88 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.packet; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import packetproxy.quic.value.packet.QuicPacket; +import packetproxy.quic.value.packet.longheader.pnspace.HandshakePacket; +import packetproxy.quic.value.packet.longheader.pnspace.InitialPacket; +import packetproxy.quic.value.packet.shortheader.ShortHeaderPacket; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.utils.Constants; + +@Getter +@NoArgsConstructor(access = AccessLevel.NONE) +public class QuicPacketBuilder { + + static public QuicPacketBuilder getBuilder() { + return new QuicPacketBuilder(); + } + + Constants.PnSpaceType pnSpaceType; + Constants.QuicPacketType quicPacketType; + byte[] token = new byte[0]; /* for Initial Packet */ + byte[] payload = new byte[0]; + PacketNumber packetNumber; + ConnectionIdPair connIdPair; + + public QuicPacketBuilder setPnSpaceType(Constants.PnSpaceType pnSpaceType) { + this.pnSpaceType = pnSpaceType; + return this; + } + + public QuicPacketBuilder setPacketType(Constants.QuicPacketType quicPacketType) { + this.quicPacketType = quicPacketType; + return this; + } + + public QuicPacketBuilder setToken(byte[] token) { + this.token = token; + return this; + } + + public QuicPacketBuilder setPayload(byte[] payload) { + this.payload = payload; + return this; + } + + public QuicPacketBuilder setConnectionIdPair(ConnectionIdPair connIdPair) { + this.connIdPair = connIdPair; + return this; + } + + public QuicPacketBuilder setPacketNumber(PacketNumber packetNumber) { + this.packetNumber = packetNumber; + return this; + } + + public QuicPacket build() throws Exception { + if (this.quicPacketType == Constants.QuicPacketType.PacketInitial) { + return InitialPacket.of(1, this.connIdPair, this.packetNumber, this.payload, this.token); + } + if (this.quicPacketType == Constants.QuicPacketType.PacketHandshake) { + return HandshakePacket.of(1, this.connIdPair, this.packetNumber, this.payload); + } + if (this.quicPacketType == Constants.QuicPacketType.PacketApplication) { + return ShortHeaderPacket.of(this.connIdPair.getDestConnId(), this.packetNumber, this.payload); + } + throw new Exception("error: unknown packet type"); + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/packet/QuicPacketParser.java b/src/main/java/core/packetproxy/quic/service/packet/QuicPacketParser.java new file mode 100644 index 0000000..9e0cc1a --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/packet/QuicPacketParser.java @@ -0,0 +1,124 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.packet; + +import packetproxy.quic.service.connection.Connection; +import packetproxy.quic.service.key.RoleKeys; +import packetproxy.quic.utils.AwaitingException; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.value.packet.QuicPacket; +import packetproxy.quic.value.packet.longheader.LongHeaderPacket; +import packetproxy.quic.value.packet.longheader.pnspace.HandshakePacket; +import packetproxy.quic.value.packet.longheader.pnspace.InitialPacket; +import packetproxy.quic.value.packet.longheader.pnspace.ZeroRttPacket; +import packetproxy.quic.value.packet.shortheader.ShortHeaderPacket; + +import java.net.DatagramPacket; +import java.nio.ByteBuffer; +import java.util.Optional; + +import static packetproxy.quic.utils.Constants.PnSpaceType.*; + + +public class QuicPacketParser { + + static public ConnectionId getDestConnectionId(byte[] bytes) throws Exception { + return getDestConnectionId(ByteBuffer.wrap(bytes)); + } + + static public ConnectionId getDestConnectionId(ByteBuffer buffer) throws Exception { + byte type = getTypeWithoutIncrement(buffer); + if (LongHeaderPacket.is(type)) { + return LongHeaderPacket.getDestConnId(buffer); + } else if (ShortHeaderPacket.is(type)){ + return ShortHeaderPacket.getDestConnId(buffer); + } + throw new Exception("Error: unknown packet (LongHeaderPacket nor ShortHeaderPacket)"); + } + + static private byte getTypeWithoutIncrement(ByteBuffer buffer) { + int pos = buffer.position(); + byte type = buffer.get(); + buffer.position(pos); + return type; + } + + private final Connection conn; + private final RoleKeys roleKeys; + + public QuicPacketParser(Connection conn, RoleKeys roleKeys) { + this.conn = conn; + this.roleKeys = roleKeys; + } + + public void parseOnePacket(DatagramPacket udpPacket) throws Exception { + ByteBuffer buffer = ByteBuffer.wrap(udpPacket.getData()); + while (buffer.hasRemaining()) { + this.parse(buffer).ifPresent(packet -> { + //if (this.conn.getRole() == Constants.Role.SERVER) { + // PacketProxyUtility.getInstance().packetProxyLog("[QUIC] CLIENT---> " + packet); + //} else { + // PacketProxyUtility.getInstance().packetProxyLog("[QUIC] <---SERVER " + packet); + //} + this.conn.getPnSpaces().receivePacket(packet); + }); + } + } + + private Optional parse(ByteBuffer buffer) throws Exception { + + byte type = getTypeWithoutIncrement(buffer); + + if (InitialPacket.is(type) && this.roleKeys.hasInitialKey()) { + PacketNumber largestAckedPn = this.conn.getPnSpace(PnSpaceInitial).getLargestAckedPn(); + InitialPacket initialPacket = new InitialPacket(buffer, this.roleKeys.getInitialKey(), largestAckedPn); + return Optional.of(initialPacket); + + } else if (HandshakePacket.is(type) && this.roleKeys.hasHandshakeKey()) { + PacketNumber largestAckedPn = this.conn.getPnSpace(PnSpaceHandshake).getLargestAckedPn(); + HandshakePacket handshakePacket = new HandshakePacket(buffer, this.roleKeys.getHandshakeKey(), largestAckedPn); + return Optional.of(handshakePacket); + + } else if (ShortHeaderPacket.is(type) && this.roleKeys.hasApplicationKey()) { + PacketNumber largestAckedPn = this.conn.getPnSpace(PnSpaceApplicationData).getLargestAckedPn(); + ShortHeaderPacket shortHeaderPacket = new ShortHeaderPacket(buffer, this.roleKeys.getApplicationKey(), largestAckedPn); + return Optional.of(shortHeaderPacket); + + } else if (ZeroRttPacket.is(type) && this.roleKeys.hasZeroRttKey()) { + PacketNumber largestAckedPn = this.conn.getPnSpace(PnSpaceApplicationData).getLargestAckedPn(); + ZeroRttPacket zeroRttPacket = new ZeroRttPacket(buffer, this.roleKeys.getZeroRttKey(), largestAckedPn); + return Optional.of(zeroRttPacket); + + } else if (type == 0x0) { + /* remaining data in the packet is paddings */ + buffer.position(buffer.limit()); + return Optional.empty(); + + } else { + if (HandshakePacket.is(type)) { + throw new AwaitingException("wait until deploying handshake key"); + } + if (ShortHeaderPacket.is(type) || ZeroRttPacket.is(type)) { + throw new AwaitingException("wait until deploying application key"); + } + // TODO: ZeroRttPacket, RetryPacket + throw new Exception(String.format("Error: packet type (%x) is not supported", type)); + } + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/pnspace/PnSpace.java b/src/main/java/core/packetproxy/quic/service/pnspace/PnSpace.java new file mode 100644 index 0000000..47912c5 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/pnspace/PnSpace.java @@ -0,0 +1,239 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; +import packetproxy.quic.service.connection.Connection; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.service.framegenerator.AckFrameGenerator; +import packetproxy.quic.service.framegenerator.CryptoFramesToMessages; +import packetproxy.quic.service.framegenerator.MessagesToCryptoFrames; +import packetproxy.quic.service.framegenerator.StreamFramesToMessages; +import packetproxy.quic.service.packet.QuicPacketBuilder; +import packetproxy.quic.service.pnspace.helper.LostPackets; +import packetproxy.quic.service.pnspace.helper.SendFrameQueue; +import packetproxy.quic.service.pnspace.helper.SentPackets; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.value.QuicMessage; +import packetproxy.quic.value.SentPacket; +import packetproxy.quic.value.frame.*; +import packetproxy.quic.value.packet.PnSpacePacket; +import packetproxy.quic.value.packet.QuicPacket; +import packetproxy.util.PacketProxyUtility; + +import java.io.OutputStream; +import java.time.Instant; +import java.util.List; + +import static packetproxy.quic.service.handshake.HandshakeState.State.Confirmed; +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceHandshake; +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceInitial; +import static packetproxy.util.Throwing.rethrow; + +@Getter +@NoArgsConstructor(access = AccessLevel.NONE) +public abstract class PnSpace { + + protected final CryptoFramesToMessages frameToMsgCryptoStream = new CryptoFramesToMessages(); + protected final MessagesToCryptoFrames msgToFrameCryptoStream = new MessagesToCryptoFrames(); + protected final StreamFramesToMessages frameToMsgStream = new StreamFramesToMessages(); + + protected final AckFrameGenerator ackFrameGenerator = new AckFrameGenerator(); + protected final SentPackets sentPackets = new SentPackets(); + protected final SendFrameQueue sendFrameQueue = new SendFrameQueue(); + protected final Connection conn; + protected PacketNumber largestAckedPn = PacketNumber.Infinite; + protected Instant lossTime = Instant.MAX; /* パケットが欠落することになる未来時刻 */ + protected Instant timeOfLastAckElicitingPacket = Instant.MIN; /* 最後にAckを誘発するPacketを送信した時刻 */ + protected PacketNumber nextPacketNumber; + + public PnSpace(Connection conn) { + this.conn = conn; + this.nextPacketNumber = PacketNumber.of(0); + } + + public synchronized boolean hasAnyAckElicitingPacket() { + return this.sentPackets.hasAnyAckElicitingPacket(); + } + + public synchronized void close() { + this.sentPackets.clear(); + this.sendFrameQueue.clear(); + this.lossTime = Instant.MAX; + this.timeOfLastAckElicitingPacket = Instant.MIN; + } + + public synchronized void OnAckReceived(AckFrame ackFrame) { + if (this.largestAckedPn == PacketNumber.Infinite) { + this.largestAckedPn = ackFrame.getLargestAckedPn(); + } else { + this.largestAckedPn = PacketNumber.max(this.largestAckedPn, ackFrame.getLargestAckedPn()); + } + + SentPackets newlyAckedPackets = sentPackets.detectAndRemoveAckedPackets(ackFrame); + if (newlyAckedPackets.isEmpty()) { + return; + } + + /* Update the RTT if the largest acknowledged is newly acked, and at least one ack-eliciting was newly acked. */ + newlyAckedPackets.getLargest().ifPresent(packet -> { + if (packet.getPacketNumber() == ackFrame.getLargestAckedPn() && newlyAckedPackets.hasAnyAckElicitingPacket()) { + this.conn.getRttEstimator().updateRtt(packet.getTimeSent(), ackFrame.getAckDelay()); + } + }); + + if (ackFrame instanceof AckEcnFrame) { + /* PacketProxyでは通信路の輻輳制御機能は実装しない */ + // ProcessECN(ackFrame, pn_space); + } + + LostPackets lostPackets = this.detectAndRemoveLostPackets(); + if (!lostPackets.isEmpty()) { + PacketProxyUtility.getInstance().packetProxyLogErr("[QUIC] lost packets: " + lostPackets); + OnPacketsLost(lostPackets); + } + + /* Reset pto_count unless the client is unsure if the server has validated the client's address. */ + if (this.conn.peerCompletedAddressValidation()) { + this.conn.getPto().clearPtoCount(); + } + this.conn.getLossDetection().setLossDetectionTimer(); + } + + public synchronized void receivePacket(QuicPacket packet) { + if (packet instanceof PnSpacePacket) { + PnSpacePacket pnSpacePacket = (PnSpacePacket) packet; + + this.ackFrameGenerator.received(pnSpacePacket.getPacketNumber()); + + if (pnSpacePacket.isAckEliciting()) { + this.addSendFrame(ackFrameGenerator.generateAckFrame()); + } + + this.receiveFrames(pnSpacePacket.getFrames()); + } + } + + private synchronized void receiveFrames(Frames frames) { + for (Frame frame : frames) { + if (frame instanceof CryptoFrame) { + frameToMsgCryptoStream.write((CryptoFrame) frame); + frameToMsgCryptoStream.getHandshakeMessages().forEach(rethrow(msg -> { + this.conn.getHandshake().received(msg); + })); + } else if (frame instanceof StreamFrame) { + StreamFrame streamFrame = (StreamFrame) frame; + this.frameToMsgStream.put(streamFrame); + this.frameToMsgStream.get(streamFrame.getStreamId()).ifPresent(rethrow(msg -> { + /* Application Data received from peer */ + OutputStream os = this.conn.getPipe().getRawEndpoint().getOutputStream(); + os.write(msg.getBytes()); + os.flush(); + })); + } else if (frame instanceof AckFrame) { + this.OnAckReceived((AckFrame) frame); + } else if (frame instanceof HandshakeDoneFrame) { + this.conn.getHandshakeState().transit(Confirmed); + if (this.conn.getRole() == Constants.Role.CLIENT) { + this.conn.getKeys().discardInitialKey(); + this.conn.getPnSpace(PnSpaceInitial).close(); + this.conn.getKeys().discardHandshakeKey(); + this.conn.getPnSpace(PnSpaceHandshake).close(); + } + } else if (frame instanceof ConnectionCloseFrame) { + this.conn.close(); + } else if (frame instanceof NewConnectionIdFrame) { + /* not implemented yet */ + } else if (frame instanceof NewTokenFrame) { + /* not implemented yet */ + } else if (frame instanceof PingFrame) { + /* Do Nothing */ + } else if (frame instanceof PaddingFrame) { + /* Do Nothing */ + } else { + System.err.println("Error: couldn't process frame: " + frame); + } + } + } + + public synchronized void addSendQuicMessage(QuicMessage msg) { + /* defined on ApplicationData PnSpace only */ + } + + public void addSendFrame(Frame frame) { + this.addSendFrames(Frames.of(frame)); + } + + public synchronized void addSendFrames(Frames frames) { + this.sendFrameQueue.add(frames); + this.conn.getPnSpaces().addSendPackets(this.getAndRemoveSendFramesAndConvertPacketBuilders()); + } + public abstract List getAndRemoveSendFramesAndConvertPacketBuilders(); + + public synchronized void addSentPacket(QuicPacket packet) { + if (packet instanceof PnSpacePacket) { + PnSpacePacket pnSpacePacket = (PnSpacePacket) packet; + if (pnSpacePacket.isAckEliciting()) { + this.sentPackets.add(new SentPacket(pnSpacePacket)); + this.timeOfLastAckElicitingPacket = Instant.now(); + } + } + } + + public void OnPacketsLost(LostPackets lostPackets) { + lostPackets.stream().forEach(sentPacket -> { + this.addSendFrames(sentPacket.getPacket().getFrames()); /* resend packet */ + }); + } + + public synchronized LostPackets detectAndRemoveLostPackets() { + assert (this.largestAckedPn != PacketNumber.Infinite); + this.lossTime = Instant.MAX; + + LostPackets lostPackets = new LostPackets(); + long lossDelay = this.conn.getRttEstimator().getLossDelay(); + + /* Packets sent before this time are deemed lost. */ + Instant lostSendTime = Instant.now().minusMillis(lossDelay); + + for (SentPacket unAcked : this.sentPackets.getUnAckedPackets()) { + PacketNumber largestAckedPn = this.largestAckedPn; + PacketNumber unAckedPn = unAcked.getPacketNumber(); + if (unAckedPn.isLargerThan(largestAckedPn)) { + continue; + } + /* + * Mark packet as lost, or set time when it should be marked. + * Note: The use of kPacketThreshold here assumes that there were no sender-induced gaps in the packet number space. + */ + if (unAcked.getTimeSent().isBefore(lostSendTime) || largestAckedPn.isLargerThanOrEquals(unAckedPn.plus(Constants.kPacketThreshold))) { + this.sentPackets.removePacket(unAcked); + lostPackets.add(unAcked); + } else { + Instant newlyLossTime = unAcked.getTimeSent().plusMillis(lossDelay); + if (newlyLossTime.isBefore(this.lossTime)) { + this.lossTime = newlyLossTime; + } + } + } + return lostPackets; + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/pnspace/PnSpaces.java b/src/main/java/core/packetproxy/quic/service/pnspace/PnSpaces.java new file mode 100644 index 0000000..8d9b597 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/pnspace/PnSpaces.java @@ -0,0 +1,106 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace; + +import com.google.common.collect.ImmutableList; +import lombok.SneakyThrows; +import org.apache.commons.lang3.tuple.ImmutablePair; +import packetproxy.quic.utils.Constants.PnSpaceType; +import packetproxy.quic.service.pnspace.level.ApplicationDataPnSpace; +import packetproxy.quic.service.pnspace.level.HandshakePnSpace; +import packetproxy.quic.service.pnspace.level.InitialPnSpace; +import packetproxy.quic.value.packet.QuicPacket; +import packetproxy.quic.service.packet.QuicPacketBuilder; +import packetproxy.quic.value.packet.longheader.pnspace.HandshakePacket; +import packetproxy.quic.value.packet.longheader.pnspace.InitialPacket; +import packetproxy.quic.value.packet.shortheader.ShortHeaderPacket; +import packetproxy.quic.service.connection.Connection; + +import java.time.Instant; +import java.util.*; +import java.util.concurrent.LinkedBlockingQueue; + +import static packetproxy.quic.utils.Constants.PnSpaceType.*; +import static packetproxy.util.Throwing.rethrow; + +public class PnSpaces { + + private final LinkedBlockingQueue sendPacketQueue = new LinkedBlockingQueue<>(); + private final PnSpace[] pnSpaces = new PnSpace[PnSpaceType.values().length]; + private final Connection conn; + + public PnSpaces(Connection conn) { + this.conn = conn; + this.pnSpaces[PnSpaceInitial.ordinal()] = new InitialPnSpace(conn); + this.pnSpaces[PnSpaceHandshake.ordinal()] = new HandshakePnSpace(conn); + this.pnSpaces[PnSpaceApplicationData.ordinal()] = new ApplicationDataPnSpace(conn); + } + + public PnSpace getPnSpace(PnSpaceType pnSpaceType) { + return this.pnSpaces[pnSpaceType.ordinal()]; + } + + public void receivePacket(QuicPacket packet) { + if (packet instanceof InitialPacket) { + this.pnSpaces[PnSpaceInitial.ordinal()].receivePacket(packet); + } else if (packet instanceof HandshakePacket) { + this.pnSpaces[PnSpaceHandshake.ordinal()].receivePacket(packet); + } else if (packet instanceof ShortHeaderPacket) { + this.pnSpaces[PnSpaceApplicationData.ordinal()].receivePacket(packet); + } + } + + /** + * 送信するパケットをキューに入れる + */ + public void addSendPackets(List packets) { + packets.forEach(rethrow(this.sendPacketQueue::put)); + } + + /** + * 送信するパケットをキューから取得する (Blocking) + */ + @SneakyThrows + public List pollSendPackets() { + QuicPacketBuilder builder = this.sendPacketQueue.take(); + QuicPacket packet = builder.setConnectionIdPair(this.conn.getConnIdPair()).build(); + this.conn.getPnSpace(builder.getPnSpaceType()).addSentPacket(packet); + return new ArrayList<>(List.of(packet)); + } + + public ImmutablePair getEarliestLossTimeAndSpace() { + Instant lossTime = this.pnSpaces[PnSpaceInitial.ordinal()].getLossTime(); + PnSpaceType space = PnSpaceInitial; + + for (PnSpaceType pnSpaceType : ImmutableList.of(PnSpaceHandshake, PnSpaceApplicationData)) { + if (lossTime == Instant.MIN || this.pnSpaces[pnSpaceType.ordinal()].getLossTime().isBefore(lossTime)) { + lossTime = this.pnSpaces[pnSpaceType.ordinal()].getLossTime(); + space = pnSpaceType; + } + } + return ImmutablePair.of(lossTime, space); + } + + public Instant getEarliestLossTime() { + return this.getEarliestLossTimeAndSpace().getLeft(); + } + + public boolean hasAnyAckElicitingPacket() { + return Arrays.stream(this.pnSpaces).anyMatch(PnSpace::hasAnyAckElicitingPacket); + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/pnspace/helper/LostPackets.java b/src/main/java/core/packetproxy/quic/service/pnspace/helper/LostPackets.java new file mode 100644 index 0000000..3c204bb --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/pnspace/helper/LostPackets.java @@ -0,0 +1,51 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace.helper; + +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.value.SentPacket; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.stream.Stream; + +public class LostPackets implements Iterable { + private Map lostPackets = new HashMap<>(); + + public String toString() { + return String.format("Lost %d packets", this.lostPackets.values().size()); + } + + public void add(SentPacket sentPacket) { + lostPackets.put(sentPacket.getPacketNumber(), sentPacket); + } + + public boolean isEmpty() { + return lostPackets.isEmpty(); + } + + public Stream stream() { + return lostPackets.values().stream(); + } + + @Override + public Iterator iterator() { + return lostPackets.values().iterator(); + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/pnspace/helper/SendFrameQueue.java b/src/main/java/core/packetproxy/quic/service/pnspace/helper/SendFrameQueue.java new file mode 100644 index 0000000..01f4bf7 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/pnspace/helper/SendFrameQueue.java @@ -0,0 +1,54 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace.helper; + +import lombok.Getter; +import packetproxy.quic.value.frame.Frame; +import packetproxy.quic.service.frame.Frames; + +import java.util.*; + +@Getter +public class SendFrameQueue { + + Queue frames; + + public SendFrameQueue() { + this.frames = new ArrayDeque<>(); + } + + public synchronized void add(Frame frame) { + this.frames.add(frame); + } + + public synchronized void add(Frames frames) { + this.frames.addAll(frames.getFrames()); + } + + public synchronized List pollAll() { + List frames = new ArrayList<>(); + for (Frame frame = this.frames.poll(); frame != null; frame = this.frames.poll()) { + frames.add(frame); + } + return frames; + } + + public synchronized void clear() { + this.frames.clear(); + } + +} \ No newline at end of file diff --git a/src/main/java/core/packetproxy/quic/service/pnspace/helper/SentPackets.java b/src/main/java/core/packetproxy/quic/service/pnspace/helper/SentPackets.java new file mode 100644 index 0000000..b1adf3a --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/pnspace/helper/SentPackets.java @@ -0,0 +1,99 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace.helper; + +import lombok.NoArgsConstructor; +import packetproxy.quic.value.SentPacket; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.value.packet.PnSpacePacket; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.utils.PacketNumbers; + +import java.util.*; + +@NoArgsConstructor +public class SentPackets implements Iterable { + private Map sentPacketMap = new HashMap<>(); + + public SentPackets(Collection sentPacketList) { + sentPacketList.stream().forEach(spkt -> this.sentPacketMap.put(spkt.getPacketNumber(), spkt)); + } + + public synchronized void add(SentPacket sentPacket) { + this.sentPacketMap.put(sentPacket.getPacketNumber(), sentPacket); + } + + public synchronized void sent(PnSpacePacket packet) { + this.sentPacketMap.put(packet.getPacketNumber(), new SentPacket(packet)); + } + + public synchronized boolean isEmpty() { + return this.sentPacketMap.isEmpty(); + } + + public synchronized boolean hasAnyAckElicitingPacket() { + return this.sentPacketMap.values().stream().anyMatch(SentPacket::isAckEliciting); + } + + public synchronized Optional getLargest() { + return this.sentPacketMap.values().stream().max(Comparator.comparingLong(spkt -> spkt.getPacketNumber().getNumber())); + } + + public synchronized Optional getLargestAckFrame() { + return this.sentPacketMap.values().stream() + .filter(SentPacket::hasAckFrame) + .map(spkt -> spkt.getAckFrame()) + .max(Comparator.comparingLong(ackFrame -> ackFrame.get().getLargestAcknowledged())) + .flatMap(a -> a); + } + + /** + * @return newly acked PacketNumbers + */ + public synchronized SentPackets detectAndRemoveAckedPackets(AckFrame ackFrame) { + PacketNumbers ackPns = ackFrame.getAckedPacketNumbers(); + SentPackets newlyAckedPns = new SentPackets(); + ackPns.stream().forEach(pn -> { + if (sentPacketMap.containsKey(pn)) { + SentPacket sentPacket = sentPacketMap.remove(pn); + newlyAckedPns.add(sentPacket); + } + }); + return newlyAckedPns; + } + + public void removePacket(SentPacket sentPacket) { + this.removePacket(sentPacket.getPacketNumber()); + } + + public synchronized void removePacket(PacketNumber packetNumber) { + this.sentPacketMap.remove(packetNumber); + } + + public synchronized SentPackets getUnAckedPackets() { + return new SentPackets(new ArrayList<>(this.sentPacketMap.values())); + } + + public synchronized void clear() { + this.sentPacketMap.clear(); + } + + @Override + public Iterator iterator() { + return this.sentPacketMap.values().iterator(); + } +} diff --git a/src/main/java/core/packetproxy/quic/service/pnspace/level/ApplicationDataPnSpace.java b/src/main/java/core/packetproxy/quic/service/pnspace/level/ApplicationDataPnSpace.java new file mode 100644 index 0000000..6f21ec8 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/pnspace/level/ApplicationDataPnSpace.java @@ -0,0 +1,66 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace.level; + +import lombok.Getter; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.service.framegenerator.MessagesToStreamFrames; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.service.pnspace.PnSpace; +import packetproxy.quic.value.QuicMessage; +import packetproxy.quic.value.frame.Frame; +import packetproxy.quic.service.frame.FramesBuilder; +import packetproxy.quic.service.packet.QuicPacketBuilder; +import packetproxy.quic.service.connection.Connection; + +import java.util.ArrayList; +import java.util.List; + +import static packetproxy.quic.utils.Constants.QuicPacketType.PacketApplication; + +@Getter +public class ApplicationDataPnSpace extends PnSpace { + + private final MessagesToStreamFrames msgToStreamFrames = new MessagesToStreamFrames(); + + public ApplicationDataPnSpace(Connection conn) { + super(conn); + } + + @Override + public void addSendQuicMessage(QuicMessage msg) { + this.msgToStreamFrames.put(msg); + Frames frames = this.msgToStreamFrames.get(); + super.addSendFrames(frames); + } + + @Override + public List getAndRemoveSendFramesAndConvertPacketBuilders() { + List builders = new ArrayList<>(); + for (Frame frame: sendFrameQueue.pollAll()) { + byte[] payload = new FramesBuilder().add(frame).getBytes(); + builders.add(QuicPacketBuilder.getBuilder() + .setPnSpaceType(Constants.PnSpaceType.PnSpaceApplicationData) + .setPacketType(PacketApplication) + .setPacketNumber(this.nextPacketNumber) + .setPayload(payload)); + this.nextPacketNumber = this.nextPacketNumber.plus(1); + } + return builders; + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/pnspace/level/HandshakePnSpace.java b/src/main/java/core/packetproxy/quic/service/pnspace/level/HandshakePnSpace.java new file mode 100644 index 0000000..1c83a1a --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/pnspace/level/HandshakePnSpace.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace.level; + +import lombok.Getter; +import packetproxy.quic.service.connection.Connection; +import packetproxy.quic.service.frame.FramesBuilder; +import packetproxy.quic.service.packet.QuicPacketBuilder; +import packetproxy.quic.service.pnspace.PnSpace; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.value.frame.Frame; + +import java.util.ArrayList; +import java.util.List; + +import static packetproxy.quic.service.handshake.HandshakeState.State.AckReceived; + +@Getter +public class HandshakePnSpace extends PnSpace { + + public HandshakePnSpace(Connection conn) { + super(conn); + } + + @Override + public void OnAckReceived(AckFrame ackFrame) { + super.conn.getHandshakeState().transit(AckReceived); + super.OnAckReceived(ackFrame); + } + + @Override + public List getAndRemoveSendFramesAndConvertPacketBuilders() { + List builders = new ArrayList<>(); + for (Frame frame: sendFrameQueue.pollAll()) { + byte[] payload = new FramesBuilder().add(frame).getBytes(); + builders.add(QuicPacketBuilder.getBuilder() + .setPnSpaceType(Constants.PnSpaceType.PnSpaceHandshake) + .setPacketType(Constants.QuicPacketType.PacketHandshake) + .setPacketNumber(this.nextPacketNumber) + .setPayload(payload)); + this.nextPacketNumber = this.nextPacketNumber.plus(1); + } + return builders; + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/pnspace/level/InitialPnSpace.java b/src/main/java/core/packetproxy/quic/service/pnspace/level/InitialPnSpace.java new file mode 100644 index 0000000..a89f9c2 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/pnspace/level/InitialPnSpace.java @@ -0,0 +1,63 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace.level; + +import lombok.Getter; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.service.pnspace.PnSpace; +import packetproxy.quic.value.frame.Frame; +import packetproxy.quic.service.frame.FramesBuilder; +import packetproxy.quic.value.packet.QuicPacket; +import packetproxy.quic.service.packet.QuicPacketBuilder; +import packetproxy.quic.value.packet.longheader.pnspace.InitialPacket; +import packetproxy.quic.service.connection.Connection; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class InitialPnSpace extends PnSpace { + + public InitialPnSpace(Connection conn) { + super(conn); + } + + @Override + public void receivePacket(QuicPacket packet) { + if (packet instanceof InitialPacket) { + InitialPacket ip = (InitialPacket) packet; + this.conn.updateDestConnId(ip.getSrcConnId()); + } + super.receivePacket(packet); + } + + @Override + public List getAndRemoveSendFramesAndConvertPacketBuilders() { + List builders = new ArrayList<>(); + for (Frame frame: sendFrameQueue.pollAll()) { + byte[] payload = new FramesBuilder().add(frame).addPaddingFramesToEnsure1200Bytes().getBytes(); + builders.add(QuicPacketBuilder.getBuilder() + .setPnSpaceType(Constants.PnSpaceType.PnSpaceInitial) + .setPacketType(Constants.QuicPacketType.PacketInitial) + .setPacketNumber(super.nextPacketNumber) + .setPayload(payload)); + super.nextPacketNumber = super.nextPacketNumber.plus(1); + } + return builders; + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/transportparameter/TransportParameterParser.java b/src/main/java/core/packetproxy/quic/service/transportparameter/TransportParameterParser.java new file mode 100644 index 0000000..05115e2 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/transportparameter/TransportParameterParser.java @@ -0,0 +1,67 @@ +package packetproxy.quic.service.transportparameter; + +import com.google.common.collect.Sets; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; +import packetproxy.quic.value.transportparameter.UnknownParameter; + +import javax.tools.*; +import java.lang.reflect.Modifier; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class TransportParameterParser { + + private static final String transportParameterPackage = "packetproxy.quic.value.transportparameter"; + private static final Class transportParameterClass = TransportParameter.class; + private static Map> transportParameterMap; + + static public TransportParameter parse(ByteBuffer buffer) throws Exception { + int saved = buffer.position(); + long type = VariableLengthInteger.parse(buffer).getValue(); + buffer.position(saved); + + if (transportParameterMap == null) { + createTransportParameterMap(); + } + + Class klass = transportParameterMap.get(type); + if (klass == null) { + klass = transportParameterMap.get(UnknownParameter.ID); + } + return createInstance(klass, buffer); + } + + static private TransportParameter createInstance(Class klass, ByteBuffer buffer) throws Exception { + return klass.getConstructor(ByteBuffer.class).newInstance(buffer); + } + + static private TransportParameter createInstance(Class klass) throws Exception { + return klass.getConstructor().newInstance(); + } + + private static void createTransportParameterMap() throws Exception { + transportParameterMap = new HashMap<>(); + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + JavaFileManager fm = compiler.getStandardFileManager(new DiagnosticCollector(), null, null); + + Set kind = Sets.newHashSet(JavaFileObject.Kind.CLASS); + for (JavaFileObject f : fm.list(StandardLocation.CLASS_PATH, transportParameterPackage, kind, true)) { + Path encode_file_path = Paths.get(f.getName()); + String encode_class_path = encode_file_path.toString() + .replaceAll("/",".") + .replaceFirst("^.*" + transportParameterPackage, transportParameterPackage) + .replaceAll("\\.class.*$", ""); + Class klass = Class.forName(encode_class_path); + if(transportParameterClass.isAssignableFrom(klass) && !Modifier.isAbstract(klass.getModifiers())){ + long id = klass.getField("ID").getLong(null); + transportParameterMap.put(id, (Class)klass); + } + } + } + +} diff --git a/src/main/java/core/packetproxy/quic/service/transportparameter/TransportParameters.java b/src/main/java/core/packetproxy/quic/service/transportparameter/TransportParameters.java new file mode 100644 index 0000000..225d5ef --- /dev/null +++ b/src/main/java/core/packetproxy/quic/service/transportparameter/TransportParameters.java @@ -0,0 +1,174 @@ +package packetproxy.quic.service.transportparameter; + +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import net.luminis.tls.extension.Extension; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.transportparameter.*; +import packetproxy.quic.value.transportparameter.bool.DisableActiveMigrationParameter; +import packetproxy.quic.value.transportparameter.bool.ExpGreaseQuicBitParameter; +import packetproxy.quic.value.transportparameter.bytearray.InitSrcConnIdParameter; +import packetproxy.quic.value.transportparameter.bytearray.OrigDestConnIdParameter; +import packetproxy.quic.value.transportparameter.bytearray.RetrySrcConnIdParameter; +import packetproxy.quic.value.transportparameter.bytearray.StatelessResetTokenParameter; +import packetproxy.quic.value.transportparameter.complex.PreferredAddressParameter; +import packetproxy.quic.value.transportparameter.number.*; + +import java.nio.ByteBuffer; + +@Getter +@Setter +@ToString +public class TransportParameters extends Extension { + private final Constants.Role role; + private long initMaxData = 0; + private long initMaxStreamDataBidiLocal = 0; + private long initMaxStreamDataBidiRemote = 0; + private long initMaxStreamBidi = 0; + private long initMaxStreamDataUni = 0; + private long initMaxStreamUni = 0; + private byte[] initSrcConnId = new byte[0]; + private long ackDelayExponent = 3; + private long activeConnIdLimit = 2; + private boolean disableActiveMigration = false; + private long maxAckDelay = 25; + private long oldMinAckDelay = 0; + private long expMinAckDelay = 0; + private long maxIdleTimeout = 0; + private long maxUdpPayloadSize = 65527; + private byte[] origDestConnId = new byte[0]; + private byte[] preferredAddress = new byte[0]; + private byte[] retrySrcConnId = new byte[0]; + private byte[] statelessResetToken = new byte[0]; + private long oldTimestamp = 0; + private boolean expGreaseQuicBit = false; + + public TransportParameters(Constants.Role role) { + this.role = role; + } + + public TransportParameters(Constants.Role role, byte[] bytes) throws Exception { + this(role, ByteBuffer.wrap(bytes)); + } + + public TransportParameters(Constants.Role role, ByteBuffer buffer) throws Exception { + this.role = role; + short type = buffer.getShort(); + if (type != 0x39) { + throw new Exception(String.format("[Error] not TransportParameterExtension (type: %04x)", type)); + } + + short length = buffer.getShort(); + if (length == 0) { + return; + } + + int endPosition = buffer.position() + length; + while (buffer.position() < endPosition) { + TransportParameter param = TransportParameterParser.parse(buffer); + setLocalVariableFromTransportParameter(param); + } + } + + private void setLocalVariableFromTransportParameter(TransportParameter param) { + if (param instanceof InitMaxStreamDataBidiLocalParameter) { + this.initMaxStreamDataBidiLocal = ((InitMaxStreamDataBidiLocalParameter) param).getValue(); + + } else if (param instanceof InitMaxStreamDataBidiRemoteParameter) { + this.initMaxStreamDataBidiRemote = ((InitMaxStreamDataBidiRemoteParameter) param).getValue(); + + } else if (param instanceof InitMaxStreamDataUniParameter) { + this.initMaxStreamDataUni = ((InitMaxStreamDataUniParameter) param).getValue(); + + } else if (param instanceof InitMaxStreamBidiParameter) { + this.initMaxStreamBidi = ((InitMaxStreamBidiParameter) param).getValue(); + + } else if (param instanceof InitMaxStreamUniParameter) { + this.initMaxStreamUni = ((InitMaxStreamUniParameter) param).getValue(); + + } else if (param instanceof InitMaxDataParameter) { + this.initMaxData = ((InitMaxDataParameter) param).getValue(); + + } else if (param instanceof InitSrcConnIdParameter) { + this.initSrcConnId = ((InitSrcConnIdParameter) param).getValue(); + + } else if (param instanceof AckDelayExponentParameter) { + this.ackDelayExponent = ((AckDelayExponentParameter) param).getValue(); + + } else if (param instanceof ActiveConnIdLimitParameter) { + this.activeConnIdLimit = ((ActiveConnIdLimitParameter) param).getValue(); + + } else if (param instanceof DisableActiveMigrationParameter) { + this.disableActiveMigration = true; + + } else if (param instanceof MaxAckDelayParameter) { + this.maxAckDelay = ((MaxAckDelayParameter) param).getValue(); + + } else if (param instanceof MaxIdleTimeoutParameter) { + this.maxIdleTimeout = ((MaxIdleTimeoutParameter) param).getValue(); + + } else if (param instanceof MaxUdpPayloadSizeParameter) { + this.maxUdpPayloadSize = ((MaxUdpPayloadSizeParameter) param).getValue(); + + } else if (param instanceof OrigDestConnIdParameter) { + this.origDestConnId = ((OrigDestConnIdParameter) param).getValue(); + + } else if (param instanceof PreferredAddressParameter) { + this.preferredAddress = ((PreferredAddressParameter) param).getValue(); + + } else if (param instanceof RetrySrcConnIdParameter) { + this.retrySrcConnId = ((RetrySrcConnIdParameter) param).getValue(); + + } else if (param instanceof StatelessResetTokenParameter) { + this.statelessResetToken = ((StatelessResetTokenParameter) param).getValue(); + + } else if (param instanceof OldMinAckDelayParameter) { + this.oldMinAckDelay = ((OldMinAckDelayParameter) param).getValue(); + + } else if (param instanceof ExpMinAckDelayParameter) { + this.expMinAckDelay = ((ExpMinAckDelayParameter) param).getValue(); + + } else if (param instanceof OldTimestampParameter) { + this.oldTimestamp = ((OldTimestampParameter) param).getValue(); + + } else if (param instanceof ExpGreaseQuicBitParameter) { + this.expGreaseQuicBit = true; + + } else if (param instanceof UnknownParameter) { + System.err.println(String.format("[Error] Unknown Transport Parameter: %s", param)); + } + } + + @Override + public byte[] getBytes() { + ByteBuffer paramsBuffer = ByteBuffer.allocate(1500); + paramsBuffer.put(new MaxUdpPayloadSizeParameter(this.maxUdpPayloadSize).getBytes()); + paramsBuffer.put(new InitSrcConnIdParameter(this.initSrcConnId).getBytes()); + paramsBuffer.put(new InitMaxDataParameter(this.initMaxData).getBytes()); + paramsBuffer.put(new InitMaxStreamUniParameter(this.initMaxStreamUni).getBytes()); + paramsBuffer.put(new InitMaxStreamBidiParameter(this.initMaxStreamBidi).getBytes()); + paramsBuffer.put(new InitMaxStreamDataBidiLocalParameter(this.initMaxStreamDataBidiLocal).getBytes()); + paramsBuffer.put(new InitMaxStreamDataBidiRemoteParameter(this.initMaxStreamDataBidiRemote).getBytes()); + paramsBuffer.put(new InitMaxStreamDataUniParameter(this.initMaxStreamDataUni).getBytes()); + //paramsBuffer.put(new OldMinAckDelayParameter(this.oldMinAckDelay).getBytes()); + paramsBuffer.put(new AckDelayExponentParameter(this.ackDelayExponent).getBytes()); + paramsBuffer.put(new MaxIdleTimeoutParameter(this.maxIdleTimeout).getBytes()); + if (this.role == Constants.Role.SERVER) { + paramsBuffer.put(new OrigDestConnIdParameter(this.origDestConnId).getBytes()); + paramsBuffer.put(new ActiveConnIdLimitParameter(this.activeConnIdLimit).getBytes()); + if (this.disableActiveMigration) { + paramsBuffer.put(new DisableActiveMigrationParameter().getBytes()); + } + } + paramsBuffer.flip(); + + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.putShort((short)0x39); + buffer.putShort((short)paramsBuffer.remaining()); + buffer.put(SimpleBytes.parse(paramsBuffer, paramsBuffer.remaining()).getBytes()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } +} diff --git a/src/main/java/core/packetproxy/quic/utils/AwaitingException.java b/src/main/java/core/packetproxy/quic/utils/AwaitingException.java new file mode 100644 index 0000000..0a4bb2b --- /dev/null +++ b/src/main/java/core/packetproxy/quic/utils/AwaitingException.java @@ -0,0 +1,23 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.utils; + +public class AwaitingException extends Exception { + public AwaitingException(String msg) { + super(msg); + } +} diff --git a/src/main/java/core/packetproxy/quic/utils/Constants.java b/src/main/java/core/packetproxy/quic/utils/Constants.java new file mode 100644 index 0000000..29c12d2 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/utils/Constants.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.utils; + +public class Constants { + + public enum Role { + CLIENT, + SERVER, + } + + public enum QuicPacketType { + PacketInitial, + PacketHandshake, + PacketZeroRTT, + PacketApplication, + } + + public enum PnSpaceType { + PnSpaceInitial, + PnSpaceHandshake, + PnSpaceApplicationData, + } + + static public final int CONNECTION_ID_SIZE = 8; // Max: 20 bytes + static public final int TOKEN_SIZE = 16; + static public final long kGranularity = 1; // 1ms + static public final float kTimeThreshold = 9f/8f; + static public final long kPacketThreshold = 3; + +} diff --git a/src/main/java/core/packetproxy/quic/utils/PacketNumbers.java b/src/main/java/core/packetproxy/quic/utils/PacketNumbers.java new file mode 100644 index 0000000..30b96f7 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/utils/PacketNumbers.java @@ -0,0 +1,57 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.utils; + +import packetproxy.quic.value.PacketNumber; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public class PacketNumbers { + + List packetNumberList; + + public PacketNumbers() { + this.packetNumberList = new ArrayList<>(); + } + + public boolean add(PacketNumber packetNumber) { + return this.packetNumberList.add(packetNumber); + } + + public boolean addAll(PacketNumbers packetNumbers) { + return this.packetNumberList.addAll(packetNumbers.packetNumberList); + } + + public Stream stream() { + return this.packetNumberList.stream(); + } + + public boolean isEmpty() { + return this.packetNumberList.isEmpty(); + } + + public PacketNumber largest() { + Optional pn = this.packetNumberList.stream().max(Comparator.comparingLong(PacketNumber::getNumber)); + return pn.orElse(null); + } + + +} diff --git a/src/main/java/core/packetproxy/quic/utils/ScheduledTimer.java b/src/main/java/core/packetproxy/quic/utils/ScheduledTimer.java new file mode 100644 index 0000000..7053764 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/utils/ScheduledTimer.java @@ -0,0 +1,53 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.utils; + + +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class ScheduledTimer { + + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private final Runnable onTimeout; + private ScheduledFuture future; + + public ScheduledTimer(Runnable onTimeout) { + this.onTimeout = onTimeout; + this.future = null; + } + + public synchronized void update(Instant time) { + if (time == Instant.MAX) { + this.cancel(); + } else { + long delay = Duration.between(Instant.now(), time).toMillis(); + this.future = this.scheduler.schedule(this.onTimeout, delay, TimeUnit.MILLISECONDS); + } + } + + public synchronized void cancel() { + if (this.future != null) { + this.future.cancel(false); + } + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/ConnectionId.java b/src/main/java/core/packetproxy/quic/value/ConnectionId.java new file mode 100644 index 0000000..681a4db --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/ConnectionId.java @@ -0,0 +1,54 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Value; +import org.apache.commons.codec.binary.Hex; +import packetproxy.quic.utils.Constants; + +import java.nio.ByteBuffer; +import java.security.SecureRandom; + +@Value(staticConstructor = "of") +public class ConnectionId { + + static public ConnectionId generateRandom() { + byte[] connId = new byte[Constants.CONNECTION_ID_SIZE]; + new SecureRandom().nextBytes(connId); + return new ConnectionId(connId); + } + + static public ConnectionId parse(ByteBuffer buffer, long length) { + byte[] connId = SimpleBytes.parse(buffer, length).getBytes(); + return new ConnectionId(connId); + } + + @Getter(AccessLevel.NONE) + byte[] connId; + + + public byte[] getBytes() { + return connId; + } + + @Override + public String toString() { + return String.format("ConnectionId([%s])", Hex.encodeHexString(this.connId)); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/ConnectionIdPair.java b/src/main/java/core/packetproxy/quic/value/ConnectionIdPair.java new file mode 100644 index 0000000..edb6f64 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/ConnectionIdPair.java @@ -0,0 +1,30 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class ConnectionIdPair { + + static public ConnectionIdPair generateRandom() { + return ConnectionIdPair.of(ConnectionId.generateRandom(), ConnectionId.generateRandom()); + } + + ConnectionId srcConnId; + ConnectionId destConnId; +} diff --git a/src/main/java/core/packetproxy/quic/value/FixedLengthPrecededBytes.java b/src/main/java/core/packetproxy/quic/value/FixedLengthPrecededBytes.java new file mode 100644 index 0000000..858912f --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/FixedLengthPrecededBytes.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.Getter; + +import java.nio.ByteBuffer; + +@Getter +public class FixedLengthPrecededBytes { + + static public FixedLengthPrecededBytes of(byte[] bytes) { + return new FixedLengthPrecededBytes(bytes); + } + + static public FixedLengthPrecededBytes parse(ByteBuffer buffer) { + byte length = buffer.get(); + byte[] bytes = new byte[length]; + buffer.get(bytes); + return new FixedLengthPrecededBytes(bytes); + } + + private byte[] bytes; + + private FixedLengthPrecededBytes(byte[] bytes) { + this.bytes = bytes; + } + + public byte[] serialize() { + ByteBuffer buffer = ByteBuffer.allocate(255); + buffer.put((byte)bytes.length); + buffer.put(bytes); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/PacketNumber.java b/src/main/java/core/packetproxy/quic/value/PacketNumber.java new file mode 100644 index 0000000..32f44fe --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/PacketNumber.java @@ -0,0 +1,89 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.Value; + +@Value +public class PacketNumber { + + static public final PacketNumber Infinite = new PacketNumber(); + + static public PacketNumber of(long number) { + return new PacketNumber(number); + } + + static public PacketNumber max(PacketNumber a, PacketNumber b) { + return a.getNumber() > b.getNumber() ? a : b; + } + + @Getter(AccessLevel.NONE) + boolean infinite; + long number; + + private PacketNumber() { + this.number = -1; + this.infinite = true; + } + + private PacketNumber(long number) { + assert(number >= 0); + this.number = number; + this.infinite = false; + } + + public boolean isInfinite() { + return infinite; + } + + public TruncatedPacketNumber getTruncatedPacketNumber(PacketNumber largestAckedPn) { + if (!this.infinite) { + return new TruncatedPacketNumber(this, largestAckedPn); + } + return null; + } + + @SneakyThrows + public PacketNumber plus(long num) { + return new PacketNumber(this.number + num); + } + + @SneakyThrows + public PacketNumber minus(long num) { + return new PacketNumber(this.number - num); + } + + public long minus(PacketNumber packetPn) { + return this.number - packetPn.number; + } + + public boolean isLargerThan(PacketNumber packetPn) { + return this.number > packetPn.getNumber(); + } + + public boolean isLargerThanOrEquals(PacketNumber packetPn) { + return this.number >= packetPn.getNumber(); + } + + public String toString() { + return "PacketNumber(" + (this.infinite ? "INF": this.number) + ")"; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/QuicMessage.java b/src/main/java/core/packetproxy/quic/value/QuicMessage.java new file mode 100644 index 0000000..b9e9851 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/QuicMessage.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.AllArgsConstructor; +import lombok.Value; +import org.apache.commons.codec.binary.Hex; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +/* +QuicMessage { + streamId: 8 bytes + dataLength: 8 bytes + data: ... +} +*/ +@AllArgsConstructor(staticName = "of") +@Value +public class QuicMessage { + + static public List parse(ByteBuffer buffer) { + List msgs = new ArrayList<>(); + while (buffer.remaining() > 16) { + int savedPosition = buffer.position(); + long streamId = buffer.getLong(); + long dataLength = buffer.getLong(); + if (buffer.remaining() < dataLength) { + buffer.position(savedPosition); + break; + } + byte[] data = SimpleBytes.parse(buffer, dataLength).getBytes(); + msgs.add(new QuicMessage(streamId, data)); + } + return msgs; + } + + long streamId; + byte[] data; + + /** + * @return data to be passed to encoder module + * streamId: 8 bytes + * dataLength: 8 bytes + * data: x bytes + */ + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(data.length + 16); + buffer.putLong(this.streamId); + buffer.putLong(this.data.length); + buffer.put(data); + return buffer.array(); + } + + @Override + public String toString() { + return String.format("QuicMessage(streamId=%d, data=[%s])", + this.streamId, + Hex.encodeHexString(this.data)); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/SentPacket.java b/src/main/java/core/packetproxy/quic/value/SentPacket.java new file mode 100644 index 0000000..be3a770 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/SentPacket.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.Value; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.value.packet.PnSpacePacket; + +import java.time.Instant; +import java.util.Optional; + +@Value +public class SentPacket { + + PacketNumber packetNumber; + Instant timeSent; + PnSpacePacket packet; + + public SentPacket(PnSpacePacket packet) { + this.packet = packet; + this.packetNumber = packet.getPacketNumber(); + this.timeSent = Instant.now(); + } + + public boolean isAckEliciting() { + return this.packet.isAckEliciting(); + } + + public boolean hasAckFrame() { + return this.packet.hasAckFrame(); + } + + public Optional getAckFrame() { + return this.packet.getAckFrame(); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/SimpleBytes.java b/src/main/java/core/packetproxy/quic/value/SimpleBytes.java new file mode 100644 index 0000000..ec7685b --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/SimpleBytes.java @@ -0,0 +1,39 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.Value; + +import java.nio.ByteBuffer; + +@Value +public class SimpleBytes { + + static public SimpleBytes parse(ByteBuffer buffer, long sizeOfBytes) { + byte[] bytes = new byte[(int)sizeOfBytes]; + buffer.get(bytes); + return new SimpleBytes(bytes); + } + + static public SimpleBytes parse(ByteBuffer buffer, int sizeOfBytes) { + byte[] bytes = new byte[sizeOfBytes]; + buffer.get(bytes); + return new SimpleBytes(bytes); + } + + byte[] bytes; +} diff --git a/src/main/java/core/packetproxy/quic/value/Token.java b/src/main/java/core/packetproxy/quic/value/Token.java new file mode 100644 index 0000000..554b13c --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/Token.java @@ -0,0 +1,47 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Value; +import org.apache.commons.codec.binary.Hex; + +import java.security.SecureRandom; + +@Value(staticConstructor = "of") +public class Token { + + static public Token generateRandom(int size) { + byte[] token = new byte[size]; + new SecureRandom().nextBytes(token); + return new Token(token); + } + + @Getter(AccessLevel.NONE) + byte[] token; + + public byte[] getBytes() { + return token; + } + + @Override + public String toString() { + return String.format("Token([%s])", Hex.encodeHexString(this.token)); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/TruncatedPacketNumber.java b/src/main/java/core/packetproxy/quic/value/TruncatedPacketNumber.java new file mode 100644 index 0000000..f2d4b8f --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/TruncatedPacketNumber.java @@ -0,0 +1,135 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Value; +import org.apache.commons.codec.binary.Hex; + +import java.nio.ByteBuffer; + +@Value +public class TruncatedPacketNumber { + + @Getter(AccessLevel.NONE) + byte[] truncatedPacketNumber; + + public TruncatedPacketNumber(byte[] truncatedPacketNumber) { + this.truncatedPacketNumber = truncatedPacketNumber; + } + + public TruncatedPacketNumber(PacketNumber packetNumber, PacketNumber largestAckPn) { + this.truncatedPacketNumber = this.encode(packetNumber, largestAckPn); + } + + static public byte[] unmaskTruncatedPacketNumber(byte[] truncatedPacketNumber, byte[] maskKey) { + return maskTruncatedPacketNumber(truncatedPacketNumber, maskKey); + } + + static public byte[] maskTruncatedPacketNumber(byte[] truncatedPacketNumber, byte[] maskKey) { + ByteBuffer pn = ByteBuffer.allocate(truncatedPacketNumber.length); + for (int i = 0; i < truncatedPacketNumber.length; i++) { + pn.put((byte)(truncatedPacketNumber[i] ^ maskKey[1+i])); + } + pn.flip(); + return pn.array(); + } + + public byte[] getBytes() { + return truncatedPacketNumber; + } + + public PacketNumber getPacketNumber(PacketNumber largestAckedPn) { + long packetNumber = decode(this.truncatedPacketNumber, largestAckedPn); + return PacketNumber.of(packetNumber); + } + + public String toString() { + return String.format("TruncatedPacketNumber([%s])", Hex.encodeHexString(this.truncatedPacketNumber)); + } + + /* https://www.rfc-editor.org/rfc/rfc9000.html#section-a.2 */ + static private byte[] encode(PacketNumber packetNumber, PacketNumber largestAckPn) { + /* + * The number of bits must be at least one more + * than the base-2 logarithm of the number of contiguous + * unacknowledged packet numbers, including the new packet. + */ + long numUnAcked = (largestAckPn.isInfinite()) ? + packetNumber.getNumber() + 1: + packetNumber.getNumber() - largestAckPn.getNumber(); + + double minBits = Math.log(numUnAcked) / Math.log(2) + 1; + int numBytes = (int)Math.ceil(minBits / 8); + + /* truncate to the least significant bytes. */ + return truncate(packetNumber.getNumber(), numBytes); + } + + /* https://www.rfc-editor.org/rfc/rfc9000.html#section-a.1 */ + static private long decode(byte[] truncatedPacketNumberBytes, PacketNumber largestAckPn) { + long truncatedPn = bytesToLong(truncatedPacketNumberBytes); + int bits = truncatedPacketNumberBytes.length * 8; + long expectedPn = largestAckPn.getNumber() + 1; + long pnWindow = 1L << bits; + long pnHalfWindow = pnWindow / 2; + long pnMask = ~(pnWindow - 1); + + long candidatePn = (expectedPn & pnMask) | truncatedPn; + if (candidatePn <= expectedPn - pnHalfWindow && candidatePn < (1L << 62) - pnWindow) { + return candidatePn + pnWindow; + } + if (candidatePn > expectedPn + pnHalfWindow && candidatePn >= pnWindow) { + return candidatePn - pnWindow; + } + return candidatePn; + } + + static private long bytesToLong(byte[] bytes) { + switch (bytes.length) { + case 1: + return bytes[0] & 0xff; + case 2: + return ((bytes[0] & 0xff) << 8) | (bytes[1] & 0xff); + case 3: + return ((bytes[0] & 0xff) << 16) | ((bytes[1] & 0xff) << 8) | (bytes[2] & 0xff); + case 4: + return ((long) (bytes[0] & 0xff) << 24) | ((bytes[1] & 0xff) << 16) | ((bytes[2] & 0xff) << 8) | (bytes[3] & 0xff); + default: + System.err.println("[Error] can't decode packetNumber from ByteArray to Long"); + return 0; + } + } + + static private byte[] truncate(long packetNumber, int byteLength) { + if (byteLength == 1) { + return new byte[] { (byte)(packetNumber & 0xff) }; + } + else if (byteLength == 2) { + return new byte[] { (byte)((packetNumber >> 8) & 0xff), (byte)(packetNumber & 0xff) }; + } + else if (byteLength == 3) { + return new byte[] { (byte)((packetNumber >> 16) & 0xff), (byte)((packetNumber >> 8) & 0xff), (byte)(packetNumber & 0xff) }; + } + else if (byteLength >= 4) { + return new byte[] { (byte)((packetNumber >> 24) & 0xff), (byte)((packetNumber >> 16) & 0xff), (byte)((packetNumber >> 8) & 0xff), (byte)(packetNumber & 0xff) }; + } + System.err.println(String.format("[Error] can't encode packetNumber from Long to ByteArray (byteLength=%d)", byteLength)); + return null; + } +} diff --git a/src/main/java/core/packetproxy/quic/value/VariableLengthInteger.java b/src/main/java/core/packetproxy/quic/value/VariableLengthInteger.java new file mode 100644 index 0000000..0cb58be --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/VariableLengthInteger.java @@ -0,0 +1,112 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.Value; + +import java.nio.ByteBuffer; + +/* +https://datatracker.ietf.org/doc/html/draft-ietf-quic-transport-20#section-16 ++------+--------+-------------+-----------------------+ +| 2Bit | Length | Usable Bits | Range | ++------+--------+-------------+-----------------------+ +| 00 | 1 | 6 | 0-63 | +| 01 | 2 | 14 | 0-16383 | +| 10 | 4 | 30 | 0-1073741823 | +| 11 | 8 | 62 | 0-4611686018427387903 | ++------+--------+-------------+-----------------------+ +*/ +@Value +public class VariableLengthInteger { + + static public VariableLengthInteger of(long value) { + return new VariableLengthInteger(value); + } + + static public VariableLengthInteger parse(byte[] bytes) { + int len = bytes.length; + long val = 0; + for (int i = 0; i < len; i++) { + if (i == 0) { + val = bytes[0] & 0x3f; + } else { + val = (val << 8) | (((long)bytes[i]) & 0xff); + } + } + return new VariableLengthInteger(val); + } + + static public VariableLengthInteger parse(ByteBuffer buffer) { + byte byte0 = buffer.get(); + int length = estimateLength(byte0); + buffer.position(buffer.position() - 1); + return parse(SimpleBytes.parse(buffer, length).getBytes()); + } + + static private int estimateLength(byte byte0) { + switch (byte0 & 0xc0) { + case 0x00: + return 1; + case 0x40: + return 2; + case 0x80: + return 4; + case 0xc0: + return 8; + default: + return -1; // never reach here + } + } + + + static private int estimateLength(long value) { + assert(0 <= value && value < 0x4000000000000000L); + if (value < 0x40L) { + return 1; + } else if (value < 0x4000L) { + return 2; + } else if (value < 0x40000000L) { + return 4; + } else { + return 8; + } + } + + long value; + + public byte[] getBytes() { + int len = estimateLength(this.value); + ByteBuffer buf = ByteBuffer.allocate(len); + switch (len) { + case 1: + buf.put( (byte)this.value ); + break; + case 2: + buf.putShort( (short)(((short)this.value) | 0x4000) ); + break; + case 4: + buf.putInt( ((int)this.value) | 0x80000000 ); + break; + case 8: + buf.putLong(this.value | 0xC000000000000000L); + break; + } + return buf.array(); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/VariableLengthPrecededBytes.java b/src/main/java/core/packetproxy/quic/value/VariableLengthPrecededBytes.java new file mode 100644 index 0000000..e1d36b4 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/VariableLengthPrecededBytes.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import lombok.Getter; +import org.apache.commons.lang3.ArrayUtils; + +import java.nio.ByteBuffer; + +@Getter +public class VariableLengthPrecededBytes { + + static public VariableLengthPrecededBytes of(byte[] bytes) { + return new VariableLengthPrecededBytes(bytes); + } + + static public VariableLengthPrecededBytes parse(ByteBuffer buffer) { + long length = VariableLengthInteger.parse(buffer).getValue(); + byte[] bytes = new byte[]{}; + if (length > 0) { + bytes = new byte[(int) length]; + buffer.get(bytes); + } + return new VariableLengthPrecededBytes(bytes); + } + + private byte[] bytes; + + private VariableLengthPrecededBytes(byte[] bytes) { + this.bytes = bytes; + } + + public byte[] serialize() { + return ArrayUtils.addAll(VariableLengthInteger.of(bytes.length).getBytes(), bytes); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/AckEcnFrame.java b/src/main/java/core/packetproxy/quic/value/frame/AckEcnFrame.java new file mode 100644 index 0000000..c33f376 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/AckEcnFrame.java @@ -0,0 +1,105 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.*; +import packetproxy.quic.value.frame.helper.AckRanges; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + + +/* RFC9000 19.3 +ACK Frame { + Type (i) = 0x02..0x03, + Largest Acknowledged (i), + ACK Delay (i), + ACK Range Count (i), + First ACK Range (i), + ACK Range (..) ..., + [ECN Counts (..)], // if type is 0x03 +} + +ACK Range { + Gap (i), + ACK Range Length (i), +} + +ECN Counts { + ECT0 Count (i), + ECT1 Count (i), + ECN-CE Count (i), +} +*/ + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class AckEcnFrame extends AckFrame { + + static public final byte TYPE = 0x03; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + long etc0Count; + long etc1Count; + long ecnCeCount; + + static public AckEcnFrame parse(byte[] bytes) { + return AckEcnFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public AckEcnFrame parse(ByteBuffer buffer) { + AckFrame ackFrame = AckFrame.parse(buffer); + long etc0Count = VariableLengthInteger.parse(buffer).getValue(); + long etc1Count = VariableLengthInteger.parse(buffer).getValue(); + long ecnCeCount = VariableLengthInteger.parse(buffer).getValue(); + return new AckEcnFrame( + ackFrame.getLargestAcknowledged(), + ackFrame.getAckDelay(), + ackFrame.getAckRangeCount(), + ackFrame.getFirstAckRange(), + ackFrame.getAckRanges(), + etc0Count, + etc1Count, + ecnCeCount); + } + + public AckEcnFrame(long largestAcknowledged, + long ackDelay, + long ackRangeCount, + long firstAckRange, + AckRanges ackRanges, + long etc0Count, + long etc1Count, + long ecnCeCount) { + super(largestAcknowledged, ackDelay, ackRangeCount, firstAckRange, ackRanges); + this.etc0Count = etc0Count; + this.etc1Count = etc1Count; + this.ecnCeCount = ecnCeCount; + } + + @Override + public boolean isAckEliciting() { + return false; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/AckFrame.java b/src/main/java/core/packetproxy/quic/value/frame/AckFrame.java new file mode 100644 index 0000000..57ab3b5 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/AckFrame.java @@ -0,0 +1,118 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.*; +import lombok.experimental.NonFinal; +import packetproxy.quic.value.frame.helper.AckRanges; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.utils.PacketNumbers; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + + +/* RFC9000 19.3 +ACK Frame { + Type (i) = 0x02..0x03, + Largest Acknowledged (i), + ACK Delay (i), + ACK Range Count (i), + First ACK Range (i), + ACK Range (..) ..., + [ECN Counts (..)], // if type is 0x03 +} + +ACK Range { + Gap (i), + ACK Range Length (i), +} + +ECN Counts { + ECT0 Count (i), + ECT1 Count (i), + ECN-CE Count (i), +} +*/ + +@Value +@NonFinal +@EqualsAndHashCode(callSuper = true) +public class AckFrame extends Frame { + + static public final byte TYPE = 0x02; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + long largestAcknowledged; + long ackDelay; + long ackRangeCount; + long firstAckRange; + AckRanges ackRanges; + + static public AckFrame parse(byte[] bytes) { + return AckFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public AckFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + long largestAcknowledged = VariableLengthInteger.parse(buffer).getValue(); + long ackDelay = VariableLengthInteger.parse(buffer).getValue(); + long ackRangeCount = VariableLengthInteger.parse(buffer).getValue(); + long firstAckRange = VariableLengthInteger.parse(buffer).getValue(); + AckRanges ackRanges = new AckRanges(buffer, ackRangeCount); + return new AckFrame(largestAcknowledged, ackDelay, ackRangeCount, firstAckRange, ackRanges); + } + + @SneakyThrows + public PacketNumber getLargestAckedPn() { + return PacketNumber.of(this.largestAcknowledged); + } + + public PacketNumbers getAckedPacketNumbers() { + PacketNumbers pns = new PacketNumbers(); + for (long pn = this.largestAcknowledged; pn >= this.largestAcknowledged - this.firstAckRange; pn--) { + pns.add(PacketNumber.of(pn)); + } + pns.addAll(ackRanges.getAckPacketNumbers(this.largestAcknowledged - this.firstAckRange - 1)); + return pns; + } + + @Override + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(TYPE); + buffer.put(VariableLengthInteger.of(this.largestAcknowledged).getBytes()); + buffer.put(VariableLengthInteger.of(this.ackDelay).getBytes()); + buffer.put(VariableLengthInteger.of(this.ackRanges.size()).getBytes()); + buffer.put(VariableLengthInteger.of(this.firstAckRange).getBytes()); + buffer.put(ackRanges.serialize()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + @Override + public boolean isAckEliciting() { + return false; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/ConnectionCloseFrame.java b/src/main/java/core/packetproxy/quic/value/frame/ConnectionCloseFrame.java new file mode 100644 index 0000000..e5b15d7 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/ConnectionCloseFrame.java @@ -0,0 +1,74 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class ConnectionCloseFrame extends Frame { + + static public List supportedTypes() { + return ImmutableList.of((byte)0x1c, (byte)0x1d); + } + + static public ConnectionCloseFrame parse(byte[] bytes) { + return ConnectionCloseFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public ConnectionCloseFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(supportedTypes().stream().anyMatch(t -> t == type)); + long errorCode = VariableLengthInteger.parse(buffer).getValue(); + long frameType = (type == (byte)0x1c) ? VariableLengthInteger.parse(buffer).getValue() : 0; + long reasonPhraseLength = VariableLengthInteger.parse(buffer).getValue(); + byte[] reasonPhrase = SimpleBytes.parse(buffer, reasonPhraseLength).getBytes(); + return new ConnectionCloseFrame(type, errorCode, frameType, reasonPhrase); + } + + byte type; + long errorCode; + long frameType; + byte[] reasonPhrase; + + @Override + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(type); + buffer.putLong(errorCode); + if (type == (byte)0x1c) { + buffer.putLong(frameType); + } + buffer.put(VariableLengthInteger.of(reasonPhrase.length).getBytes()); + buffer.put(reasonPhrase); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + @Override + public boolean isAckEliciting() { + return false; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/CryptoFrame.java b/src/main/java/core/packetproxy/quic/value/frame/CryptoFrame.java new file mode 100644 index 0000000..487f800 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/CryptoFrame.java @@ -0,0 +1,79 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.commons.codec.binary.Hex; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class CryptoFrame extends Frame { + + static public final byte TYPE = 0x06; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + long offset; + + byte[] data; + + static public CryptoFrame parse(byte[] bytes) { + return CryptoFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public CryptoFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + long offset = VariableLengthInteger.parse(buffer).getValue(); + long length = VariableLengthInteger.parse(buffer).getValue(); + byte[] data = SimpleBytes.parse(buffer, length).getBytes(); + return new CryptoFrame(offset, data); + } + + @Override + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(TYPE); + buffer.put(VariableLengthInteger.of(this.offset).getBytes()); + buffer.put(VariableLengthInteger.of(this.data.length).getBytes()); + buffer.put(data); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + @Override + public String toString() { + return String.format("CryptFrame(offset=%d, length=%d, data=%s)", + this.offset, + this.data.length, + Hex.encodeHexString(this.data)); + } + + @Override + public boolean isAckEliciting() { + return true; + } +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/Frame.java b/src/main/java/core/packetproxy/quic/value/frame/Frame.java new file mode 100644 index 0000000..2293390 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/Frame.java @@ -0,0 +1,27 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import lombok.Value; +import lombok.experimental.NonFinal; + +@NonFinal +@Value +public abstract class Frame { + public abstract byte[] getBytes(); + public abstract boolean isAckEliciting(); +} \ No newline at end of file diff --git a/src/main/java/core/packetproxy/quic/value/frame/HandshakeDoneFrame.java b/src/main/java/core/packetproxy/quic/value/frame/HandshakeDoneFrame.java new file mode 100644 index 0000000..94f216a --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/HandshakeDoneFrame.java @@ -0,0 +1,56 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class HandshakeDoneFrame extends Frame { + + static public final byte TYPE = 0x1e; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + static public HandshakeDoneFrame parse(byte[] bytes) { + return HandshakeDoneFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public HandshakeDoneFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + return new HandshakeDoneFrame(); + } + + @Override + public byte[] getBytes() { + return new byte[]{ TYPE }; + } + + @Override + public boolean isAckEliciting() { + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/NewConnectionIdFrame.java b/src/main/java/core/packetproxy/quic/value/frame/NewConnectionIdFrame.java new file mode 100644 index 0000000..e1ffc8d --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/NewConnectionIdFrame.java @@ -0,0 +1,90 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.FixedLengthPrecededBytes; +import packetproxy.quic.value.Token; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +/* +https://www.rfc-editor.org/rfc/rfc9000.html#section-19.15 + +NEW_CONNECTION_ID Frame { + Type (i) = 0x18, + Sequence Number (i), + Retire Prior To (i), + Length (8), + Connection ID (8..160), + Stateless Reset Token (128), +} +*/ +@Value +@EqualsAndHashCode(callSuper = true) +public class NewConnectionIdFrame extends Frame { + + static public final byte TYPE = 0x18; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + long sequenceNumber; + long retirePriorTo; + ConnectionId connectionId; + Token stateResetToken; + + static public NewConnectionIdFrame parse(byte[] bytes) { + return NewConnectionIdFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public NewConnectionIdFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + long sequenceNumber = VariableLengthInteger.parse(buffer).getValue(); + long retirePriorTo = VariableLengthInteger.parse(buffer).getValue(); + ConnectionId connectionId = ConnectionId.of(FixedLengthPrecededBytes.parse(buffer).getBytes()); + Token statelessResetToken = Token.of(SimpleBytes.parse(buffer, 16).getBytes()); + + return new NewConnectionIdFrame(sequenceNumber, retirePriorTo, connectionId, statelessResetToken); + } + + @Override + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(TYPE); + buffer.put(VariableLengthInteger.of(this.sequenceNumber).getBytes()); + buffer.put(VariableLengthInteger.of(this.retirePriorTo).getBytes()); + buffer.put(FixedLengthPrecededBytes.of(this.connectionId.getBytes()).serialize()); + buffer.put(this.stateResetToken.getBytes()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + @Override + public boolean isAckEliciting() { + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/NewTokenFrame.java b/src/main/java/core/packetproxy/quic/value/frame/NewTokenFrame.java new file mode 100644 index 0000000..d049ff2 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/NewTokenFrame.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.Token; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +/* +https://www.rfc-editor.org/rfc/rfc9000.html#section-19.7 + +NEW_TOKEN Frame { + Type (i) = 0x07, + Token Length (i), + Token (..), +} + */ +@Value +@EqualsAndHashCode(callSuper = true) +public class NewTokenFrame extends Frame { + + static public final byte TYPE = 0x7; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + Token token; + + static public NewTokenFrame parse(byte[] bytes) { + return NewTokenFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public NewTokenFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + long tokenLength = VariableLengthInteger.parse(buffer).getValue(); + if (tokenLength == 0) { + System.err.println("NewTokenFrame: error: FRAME_ENCODING_ERROR"); + } + Token token = Token.of(SimpleBytes.parse(buffer, tokenLength).getBytes()); + + return new NewTokenFrame(token); + } + + @Override + public byte[] getBytes() { + return new byte[]{ TYPE }; + } + + @Override + public boolean isAckEliciting() { + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/PaddingFrame.java b/src/main/java/core/packetproxy/quic/value/frame/PaddingFrame.java new file mode 100644 index 0000000..0e2c075 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/PaddingFrame.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class PaddingFrame extends Frame { + + static public final byte TYPE = 0x00; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + long length; + + static public PaddingFrame parse(byte[] bytes) { + return PaddingFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public PaddingFrame parse(ByteBuffer buffer) { + long length = 0; + while (buffer.remaining() > 0) { + byte type = buffer.get(); + assert (type == TYPE); + length++; + } + return new PaddingFrame(length); + } + + @Override + public byte[] getBytes() { + return new byte[(int)this.length]; + } + + @Override + public boolean isAckEliciting() { + return false; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/PingFrame.java b/src/main/java/core/packetproxy/quic/value/frame/PingFrame.java new file mode 100644 index 0000000..bbd4307 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/PingFrame.java @@ -0,0 +1,56 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class PingFrame extends Frame { + + static public final byte TYPE = 0x01; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + static public PingFrame parse(byte[] bytes) { + return PingFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public PingFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + return new PingFrame(); + } + + @Override + public byte[] getBytes() { + return new byte[]{ TYPE }; + } + + @Override + public boolean isAckEliciting() { + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/ResetStreamFrame.java b/src/main/java/core/packetproxy/quic/value/frame/ResetStreamFrame.java new file mode 100644 index 0000000..77e287f --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/ResetStreamFrame.java @@ -0,0 +1,81 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +/* +https://www.rfc-editor.org/rfc/rfc9000.html#section-19.4 + +RESET_STREAM Frame { + Type (i) = 0x04, + Stream ID (i), + Application Protocol Error Code (i), + Final Size (i), +} +*/ +@Value +@EqualsAndHashCode(callSuper = true) +public class ResetStreamFrame extends Frame { + + static public final byte TYPE = 0x04; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + static public ResetStreamFrame parse(byte[] bytes) { + return ResetStreamFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public ResetStreamFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + long streamId = VariableLengthInteger.parse(buffer).getValue(); + long applicationProtocolErrorCode = VariableLengthInteger.parse(buffer).getValue(); + long finalSize = VariableLengthInteger.parse(buffer).getValue(); + return new ResetStreamFrame(streamId, applicationProtocolErrorCode, finalSize); + } + + long streamId; + long applicationErrorCode; + long finalSize; + + @Override + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(TYPE); + buffer.put(VariableLengthInteger.of(this.streamId).getBytes()); + buffer.put(VariableLengthInteger.of(this.applicationErrorCode).getBytes()); + buffer.put(VariableLengthInteger.of(this.finalSize).getBytes()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + @Override + public boolean isAckEliciting(){ + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/StopSendingFrame.java b/src/main/java/core/packetproxy/quic/value/frame/StopSendingFrame.java new file mode 100644 index 0000000..1d52739 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/StopSendingFrame.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +/* +https://www.rfc-editor.org/rfc/rfc9000.html#section-19.5 + +STOP_SENDING Frame { + Type (i) = 0x05, + Stream ID (i), + Application Protocol Error Code (i), +} +*/ +@Value +@EqualsAndHashCode(callSuper = true) +public class StopSendingFrame extends Frame { + + static public final byte TYPE = 0x05; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + static public StopSendingFrame parse(byte[] bytes) { + return StopSendingFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public StopSendingFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + long streamId = VariableLengthInteger.parse(buffer).getValue(); + long applicationProtocolErrorCode = VariableLengthInteger.parse(buffer).getValue(); + return new StopSendingFrame(streamId, applicationProtocolErrorCode); + } + + long streamId; + long applicationErrorCode; + + @Override + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(TYPE); + buffer.put(VariableLengthInteger.of(this.streamId).getBytes()); + buffer.put(VariableLengthInteger.of(this.applicationErrorCode).getBytes()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + @Override + public boolean isAckEliciting(){ + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/StreamDataBlockedFrame.java b/src/main/java/core/packetproxy/quic/value/frame/StreamDataBlockedFrame.java new file mode 100644 index 0000000..9174c46 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/StreamDataBlockedFrame.java @@ -0,0 +1,62 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class StreamDataBlockedFrame extends Frame { + + static public final byte TYPE = 0x15; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + long streamId; + long maxStreamData; + + static public StreamDataBlockedFrame parse(byte[] bytes) { + return StreamDataBlockedFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public StreamDataBlockedFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + long streamId = VariableLengthInteger.parse(buffer).getValue(); + long maxStreamData = VariableLengthInteger.parse(buffer).getValue(); + return new StreamDataBlockedFrame(streamId, maxStreamData); + } + + @Override + public byte[] getBytes() { + return new byte[]{ TYPE }; + } + + @Override + public boolean isAckEliciting() { + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/StreamFrame.java b/src/main/java/core/packetproxy/quic/value/frame/StreamFrame.java new file mode 100644 index 0000000..88550d4 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/StreamFrame.java @@ -0,0 +1,111 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.commons.codec.binary.Hex; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Value(staticConstructor = "of") +public class StreamFrame extends Frame { + + static public boolean is(byte type) { + return supportedTypes().stream().anyMatch(t -> t == type); + } + + static public List supportedTypes() { + return ImmutableList.of((byte)0x08, (byte)0x09, (byte)0x0a, (byte)0x0b, (byte)0x0c, (byte)0x0d, (byte)0x0e, (byte)0x0f); + } + + static private boolean hasOffsetField(byte type) { + return (type & 0x04) > 0; + } + + static private boolean hasLengthField(byte type) { + return (type & 0x02) > 0; + } + + static private boolean hasFinishBit(byte type) { + return (type & 0x01) > 0; + } + + static public StreamFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(StreamFrame.is(type)); + + long streamId = VariableLengthInteger.parse(buffer).getValue(); + long offset = hasOffsetField(type) ? VariableLengthInteger.parse(buffer).getValue() : 0; + long length = hasLengthField(type) ? VariableLengthInteger.parse(buffer).getValue() : 0; + boolean finished = hasFinishBit(type); + byte[] streamData; + if (length > 0) { + streamData = SimpleBytes.parse(buffer, length).getBytes(); + } else { + streamData = SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + length = streamData.length; + } + return StreamFrame.of(streamId, offset, length, streamData, finished); + } + + long streamId; + long offset; + long length; + byte[] streamData; + boolean finished; + + @Override + public String toString() { + return String.format("StreamFrame(streamId=0x%x, offset=%d, length=%d, data=[%s])", + this.streamId, this.offset, this.length, Hex.encodeHexString(this.streamData)); + } + + private byte getType() { + byte offsetBit = (this.offset > 0) ? (byte) 0x04 : 0x00; + byte lengthBit = (this.length > 0) ? (byte) 0x02 : 0x00; + byte finishBit = this.finished ? (byte) 0x01: 0x00; + return (byte)(0x08 | offsetBit | lengthBit | finishBit); + } + + @Override + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(this.getType()); + buffer.put(VariableLengthInteger.of(this.streamId).getBytes()); + if (this.offset > 0) { + buffer.put(VariableLengthInteger.of(this.offset).getBytes()); + } + if (this.length > 0) { + buffer.put(VariableLengthInteger.of(this.length).getBytes()); + } + buffer.put(this.streamData); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + @Override + public boolean isAckEliciting() { + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/StreamsBlockedBidiFrame.java b/src/main/java/core/packetproxy/quic/value/frame/StreamsBlockedBidiFrame.java new file mode 100644 index 0000000..4b920cd --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/StreamsBlockedBidiFrame.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class StreamsBlockedBidiFrame extends Frame { + + static public final byte TYPE = 0x16; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + long maxStreams; + + static public StreamsBlockedBidiFrame parse(byte[] bytes) { + return StreamsBlockedBidiFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public StreamsBlockedBidiFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + long maxStreams = VariableLengthInteger.parse(buffer).getValue(); + return new StreamsBlockedBidiFrame(maxStreams); + } + + @Override + public byte[] getBytes() { + return new byte[]{ TYPE }; + } + + @Override + public boolean isAckEliciting() { + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/StreamsBlockedUniFrame.java b/src/main/java/core/packetproxy/quic/value/frame/StreamsBlockedUniFrame.java new file mode 100644 index 0000000..7d618ee --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/StreamsBlockedUniFrame.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class StreamsBlockedUniFrame extends Frame { + + static public final byte TYPE = 0x17; + + static public List supportedTypes() { + return ImmutableList.of(TYPE); + } + + long maxStreams; + + static public StreamsBlockedUniFrame parse(byte[] bytes) { + return StreamsBlockedUniFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public StreamsBlockedUniFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + assert(type == TYPE); + long maxStreams = VariableLengthInteger.parse(buffer).getValue(); + return new StreamsBlockedUniFrame(maxStreams); + } + + @Override + public byte[] getBytes() { + return new byte[]{ TYPE }; + } + + @Override + public boolean isAckEliciting() { + return true; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/UnknownFrame.java b/src/main/java/core/packetproxy/quic/value/frame/UnknownFrame.java new file mode 100644 index 0000000..35cc67e --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/UnknownFrame.java @@ -0,0 +1,60 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import com.google.common.collect.ImmutableList; +import lombok.EqualsAndHashCode; +import lombok.Value; + +import java.nio.ByteBuffer; +import java.util.List; + +@Value +@EqualsAndHashCode(callSuper = true) +public class UnknownFrame extends Frame { + + static public List supportedTypes() { + return ImmutableList.of(); + } + + byte type; + + static public UnknownFrame parse(byte[] bytes) { + return UnknownFrame.parse(ByteBuffer.wrap(bytes)); + } + + static public UnknownFrame parse(ByteBuffer buffer) { + byte type = buffer.get(); + return new UnknownFrame(type); + } + + @Override + public byte[] getBytes() { + return new byte[] { this.type }; + } + + @Override + public boolean isAckEliciting() { + return false; + } + + @Override + public String toString() { + return String.format("Unknown(type=%02x)", this.type); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/helper/AckRange.java b/src/main/java/core/packetproxy/quic/value/frame/helper/AckRange.java new file mode 100644 index 0000000..23d1e43 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/helper/AckRange.java @@ -0,0 +1,63 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame.helper; + +import lombok.AllArgsConstructor; +import lombok.Value; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.utils.PacketNumbers; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; + +@AllArgsConstructor +@Value +public class AckRange { + long gap; + long ackRangeLength; + + public AckRange(ByteBuffer buffer) { + this.gap = VariableLengthInteger.parse(buffer).getValue(); + this.ackRangeLength = VariableLengthInteger.parse(buffer).getValue(); + } + + public long size() { + return this.gap + this.ackRangeLength + 2; + } + + public PacketNumbers getAckPacketNumbers(long largestGapPn) { + long largestAckPn = largestGapPn - this.gap - 1; + PacketNumbers pns = new PacketNumbers(); + for (long pn = largestAckPn; pn >= largestAckPn - this.ackRangeLength; pn--) { + pns.add(PacketNumber.of(pn)); + } + return pns; + } + + public byte[] serialize() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(VariableLengthInteger.of(this.gap).getBytes()); + buffer.put(VariableLengthInteger.of(this.ackRangeLength).getBytes()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + public String toString() { + return String.format("gap:%d, ackRangeLength:%d", this.gap, this.ackRangeLength); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/frame/helper/AckRanges.java b/src/main/java/core/packetproxy/quic/value/frame/helper/AckRanges.java new file mode 100644 index 0000000..fa03c37 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/frame/helper/AckRanges.java @@ -0,0 +1,90 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame.helper; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import packetproxy.quic.utils.PacketNumbers; +import packetproxy.quic.value.SimpleBytes; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +@Getter +@EqualsAndHashCode +public class AckRanges implements Iterable { + static public final AckRanges emptyAckRanges = new AckRanges(); + + private List ackRanges; + + public AckRanges(ByteBuffer buffer, long rangeCount) { + this.ackRanges = new ArrayList<>(); + for (long i = 0; i < rangeCount; i++) { + this.ackRanges.add(new AckRange(buffer)); + } + } + + public AckRanges(List ackRanges) { + this.ackRanges = ackRanges; + } + + private AckRanges() { + this.ackRanges = Collections.emptyList(); + } + + public PacketNumbers getAckPacketNumbers(long largestGapPn) { + PacketNumbers pns = new PacketNumbers(); + for (AckRange ackRange : ackRanges) { + pns.addAll(ackRange.getAckPacketNumbers(largestGapPn)); + largestGapPn -= ackRange.size(); + } + return pns; + } + + public byte[] serialize() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + for (AckRange ackRange: this.ackRanges) { + buffer.put(ackRange.serialize()); + } + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + public String toString() { + String rangeMsg = ""; + for (AckRange ackRange: this.ackRanges) { + rangeMsg += ackRange + "|"; + } + return "{" + rangeMsg + "}"; + } + + public int size() { + return this.ackRanges.size(); + } + + public AckRange get(int index) { + return this.ackRanges.get(0); + } + + @Override + public Iterator iterator() { + return this.ackRanges.iterator(); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/key/Key.java b/src/main/java/core/packetproxy/quic/value/key/Key.java new file mode 100644 index 0000000..c25d1b5 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/key/Key.java @@ -0,0 +1,109 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.key; + +import at.favre.lib.crypto.HKDF; +import lombok.AllArgsConstructor; +import lombok.Value; +import lombok.experimental.NonFinal; +import org.apache.commons.codec.binary.Hex; + +import javax.crypto.Cipher; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.nio.ByteBuffer; + +@NonFinal +@Value +@AllArgsConstructor +public class Key { + static final protected byte[] STATIC_SALT_V1 = new byte[]{ + (byte) 0x38, (byte) 0x76, (byte) 0x2c, (byte) 0xf7, (byte) 0xf5, (byte) 0x59, (byte) 0x34, (byte) 0xb3, + (byte) 0x4d, (byte) 0x17, (byte) 0x9a, (byte) 0xe6, (byte) 0xa4, (byte) 0xc8, (byte) 0x0c, (byte) 0xad, + (byte) 0xcc, (byte) 0xbb, (byte) 0x7f, (byte) 0x0a}; + + static public byte[] hkdfExpandLabel(byte[] secret, String labelStr, String contextStr, short length) { + byte[] label = String.format("tls13 %s", labelStr).getBytes(); + byte[] context = contextStr.getBytes(); + HKDF hkdf = HKDF.fromHmacSha256(); + ByteBuffer hkdfLabel = ByteBuffer.allocate(2 + 1 + label.length + 1 + context.length); + hkdfLabel.putShort(length); + hkdfLabel.put((byte) label.length); + hkdfLabel.put(label); + hkdfLabel.put((byte) context.length); + hkdfLabel.put(context); + return hkdf.expand(secret, hkdfLabel.array(), length); + } + + static public Key of(byte[] secret) { + byte[] key = hkdfExpandLabel(secret, "quic key", "", (short) 16); + byte[] iv = hkdfExpandLabel(secret, "quic iv", "", (short) 12); + byte[] hp = hkdfExpandLabel(secret, "quic hp", "", (short) 16); + return new Key(secret, key, iv, hp); + } + + byte[] secret; + byte[] key; + byte[] iv; + byte[] hp; + + public byte[] getMaskForHeaderProtection(byte[] sample) throws Exception { + SecretKeySpec keySpec = new SecretKeySpec(this.hp, "AES"); + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, keySpec); + return cipher.doFinal(sample); + } + + public byte[] aesGCM(int cipherMode, byte[] packetNumber, byte[] payload, byte[] associatedData) throws Exception { + byte[] nonce = getNonce(packetNumber); + SecretKeySpec keySpec = new SecretKeySpec(this.key, "AES"); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(128, nonce); + cipher.init(cipherMode, keySpec, gcmParameterSpec); + cipher.updateAAD(associatedData); + return cipher.doFinal(payload); + } + + public byte[] decryptPayload(byte[] packetNumber, byte[] encryptPayload, byte[] associatedData) throws Exception { + return aesGCM(Cipher.DECRYPT_MODE, packetNumber, encryptPayload, associatedData); + } + + public byte[] encryptPayload(byte[] packetNumber, byte[] payload, byte[] associatedData) throws Exception { + return aesGCM(Cipher.ENCRYPT_MODE, packetNumber, payload, associatedData); + } + + private byte[] getNonce(byte[] packetNumber) { + byte[] nonce = new byte[12]; + for (int i = 0; i < 12; i++) { + nonce[i] = this.iv[i]; + if (i >= nonce.length - packetNumber.length) { + nonce[i] ^= packetNumber[i - (nonce.length - packetNumber.length)]; + } + } + return nonce; + } + + @Override + public String toString() { + return String.format("Key(secret=%s, key=%s, iv=%s, hp=%s)", + Hex.encodeHexString(this.secret), + Hex.encodeHexString(this.key), + Hex.encodeHexString(this.iv), + Hex.encodeHexString(this.hp)); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/key/level/ApplicationKey.java b/src/main/java/core/packetproxy/quic/value/key/level/ApplicationKey.java new file mode 100644 index 0000000..da5992a --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/key/level/ApplicationKey.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.key.level; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.key.Key; + +@EqualsAndHashCode(callSuper = true) +@Value +public class ApplicationKey extends Key { + + static public ApplicationKey of(byte[] secret) { + Key key = Key.of(secret); + return new ApplicationKey(key.getSecret(), key.getKey(), key.getIv(), key.getHp()); + } + + public ApplicationKey(byte[] secret, byte[] key, byte[] iv, byte[] hp) { + super(secret, key, iv, hp); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/key/level/HandshakeKey.java b/src/main/java/core/packetproxy/quic/value/key/level/HandshakeKey.java new file mode 100644 index 0000000..92f894c --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/key/level/HandshakeKey.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.key.level; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.key.Key; + +@EqualsAndHashCode(callSuper = true) +@Value +public class HandshakeKey extends Key { + + static public HandshakeKey of(byte[] secret) { + Key key = Key.of(secret); + return new HandshakeKey(key.getSecret(), key.getKey(), key.getIv(), key.getHp()); + } + + public HandshakeKey(byte[] secret, byte[] key, byte[] iv, byte[] hp) { + super(secret, key, iv, hp); + } + +} \ No newline at end of file diff --git a/src/main/java/core/packetproxy/quic/value/key/level/InitialKey.java b/src/main/java/core/packetproxy/quic/value/key/level/InitialKey.java new file mode 100644 index 0000000..bf95223 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/key/level/InitialKey.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.key.level; + +import at.favre.lib.crypto.HKDF; +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.key.Key; + +@EqualsAndHashCode(callSuper = true) +@Value +public class InitialKey extends Key { + + static public InitialKey of(Constants.Role role, ConnectionId destConnId) { + HKDF hkdf = HKDF.fromHmacSha256(); + byte[] initialSecret = hkdf.extract(STATIC_SALT_V1, destConnId.getBytes()); + byte[] secret = (role == Constants.Role.CLIENT) ? + hkdfExpandLabel(initialSecret, "client in", "", (short) 32) : + hkdfExpandLabel(initialSecret, "server in", "", (short) 32); + Key key = Key.of(secret); + return new InitialKey(key.getSecret(), key.getKey(), key.getIv(), key.getHp()); + } + + public InitialKey(byte[] secret, byte[] key, byte[] iv, byte[] hp) { + super(secret, key, iv, hp); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/key/level/ZeroRttKey.java b/src/main/java/core/packetproxy/quic/value/key/level/ZeroRttKey.java new file mode 100644 index 0000000..a1fab02 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/key/level/ZeroRttKey.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.key.level; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import packetproxy.quic.value.key.Key; + +@EqualsAndHashCode(callSuper = true) +@Value +public class ZeroRttKey extends Key { + + static public ZeroRttKey of(byte[] secret) { + Key key = Key.of(secret); + return new ZeroRttKey(key.getSecret(), key.getKey(), key.getIv(), key.getHp()); + } + + public ZeroRttKey(byte[] secret, byte[] key, byte[] iv, byte[] hp) { + super(secret, key, iv, hp); + } + +} \ No newline at end of file diff --git a/src/main/java/core/packetproxy/quic/value/packet/PnSpacePacket.java b/src/main/java/core/packetproxy/quic/value/packet/PnSpacePacket.java new file mode 100644 index 0000000..e479985 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/PnSpacePacket.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.packet; + +import packetproxy.quic.utils.Constants.PnSpaceType; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.value.PacketNumber; + +import java.util.Optional; + +public interface PnSpacePacket { + PacketNumber getPacketNumber(); + + boolean isAckEliciting(); + + boolean hasAckFrame(); + + Optional getAckFrame(); + + PnSpaceType getPnSpaceType(); + + Frames getFrames(); +} diff --git a/src/main/java/core/packetproxy/quic/value/packet/QuicPacket.java b/src/main/java/core/packetproxy/quic/value/packet/QuicPacket.java new file mode 100644 index 0000000..2a0a80b --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/QuicPacket.java @@ -0,0 +1,93 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.packet; + +import lombok.Value; +import lombok.experimental.NonFinal; + +import java.nio.ByteBuffer; + +@NonFinal +@Value +public class QuicPacket { + + byte maskedType; + @NonFinal + byte type; /* Packet number length bits are not included. all the bits should be cleared */ + @NonFinal + int origPnLength; + + protected QuicPacket(ByteBuffer buffer) { + this(buffer.get()); + } + + protected QuicPacket(byte type) { + this.maskedType = type; + this.type = type; + this.origPnLength = (type & 0x03) + 1; + } + + protected enum PacketHeaderType { + LongHeaderType, + ShortHeaderType, + } + + protected void unmaskType(PacketHeaderType headerType, byte[] maskKey) { + this.type = QuicPacket.xor(this.maskedType, headerType, maskKey); + this.origPnLength = (this.type & 0x03) + 1; + } + + static private byte xor(byte type, PacketHeaderType headerType, byte[] maskKey) { + byte leftHand; + byte rightHand; + if (headerType == PacketHeaderType.ShortHeaderType) { + leftHand = (byte) (type & (byte) 0xe0); + rightHand = (byte) ((type ^ maskKey[0]) & 0x1f); + } else { /* headerType == LongHeaderType */ + leftHand = (byte) (type & (byte) 0xf0); + rightHand = (byte) ((type ^ maskKey[0]) & 0x0f); + } + return (byte) (leftHand | rightHand); + } + + protected byte getType(int newlyPnLength) { + byte lengthCleared = (byte) (this.type & (byte)0xfc); + return (byte) (lengthCleared | (newlyPnLength - 1)); + } + + protected byte getMaskedType(int newlyPnLength, PacketHeaderType headerType, byte[] maskKey) { + byte typeWithNewPnLength = this.getType(newlyPnLength); + return QuicPacket.xor(typeWithNewPnLength, headerType, maskKey); + } + + protected byte[] getBytes() { + return new byte[] { this.getType() }; + } + + protected byte[] getBytes(int newlyPnLength) { + return new byte[] { this.getType(newlyPnLength) }; + } + + protected byte[] getMaskedBytes(int newlyPnLength, PacketHeaderType headerType, byte[] maskKey) { + return new byte[] { this.getMaskedType(newlyPnLength, headerType, maskKey) }; + } + + protected int size() { + return 1; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/packet/longheader/LongHeaderPacket.java b/src/main/java/core/packetproxy/quic/value/packet/longheader/LongHeaderPacket.java new file mode 100644 index 0000000..908668a --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/longheader/LongHeaderPacket.java @@ -0,0 +1,125 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.packet.longheader; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import lombok.experimental.NonFinal; +import packetproxy.quic.value.packet.QuicPacket; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.value.FixedLengthPrecededBytes; +import packetproxy.quic.value.SimpleBytes; + +import java.nio.ByteBuffer; + +/* +https://www.rfc-editor.org/rfc/rfc9000.html#name-long-header-packets + +Long Header Packet { + Header Form (1) = 1, + Fixed Bit (1) = 1, + Long Packet Type (2), + Type-Specific Bits (4), + Version (32), + Destination Connection ID Length (8), + Destination Connection ID (0..160), + Source Connection ID Length (8), + Source Connection ID (0..160), + Type-Specific Payload (..), +} +*/ +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +@NonFinal +@Value +public class LongHeaderPacket extends QuicPacket { + + static public boolean is(byte type) { + return (type & (byte)0xc0) == (byte)0xc0; + } + + static public ConnectionId getDestConnId(ByteBuffer buffer) { + int savedPosition = buffer.position(); + buffer.get(); + buffer.getInt(); + byte[] destConnId = FixedLengthPrecededBytes.parse(buffer).getBytes(); + buffer.position(savedPosition); + return ConnectionId.of(destConnId); + } + + protected int version; + protected ConnectionIdPair connectionIdPair; + + protected LongHeaderPacket(byte type, int version, ConnectionIdPair connIdPair) { + super(type); + this.version = version; + this.connectionIdPair = connIdPair; + } + + protected LongHeaderPacket(ByteBuffer buffer) { + super(buffer); + this.version = buffer.getInt(); + ConnectionId destConnId = ConnectionId.of(FixedLengthPrecededBytes.parse(buffer).getBytes()); + ConnectionId srcConnId = ConnectionId.of(FixedLengthPrecededBytes.parse(buffer).getBytes()); + this.connectionIdPair = ConnectionIdPair.of(srcConnId, destConnId); + } + + protected int size() { + return this.getBytes().length; + } + + protected byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(super.getType()); + buffer.putInt(this.version); + buffer.put(FixedLengthPrecededBytes.of(this.connectionIdPair.getDestConnId().getBytes()).serialize()); + buffer.put(FixedLengthPrecededBytes.of(this.connectionIdPair.getSrcConnId().getBytes()).serialize()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + protected byte[] getBytes(int newlyPnLength) { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(super.getType(newlyPnLength)); + buffer.putInt(this.version); + buffer.put(FixedLengthPrecededBytes.of(this.connectionIdPair.getDestConnId().getBytes()).serialize()); + buffer.put(FixedLengthPrecededBytes.of(this.connectionIdPair.getSrcConnId().getBytes()).serialize()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + protected byte[] getMaskedBytes(int newlyPnLength, byte[] maskKey) { + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(super.getMaskedBytes(newlyPnLength, PacketHeaderType.LongHeaderType, maskKey)); + buffer.putInt(this.version); + buffer.put(FixedLengthPrecededBytes.of(this.connectionIdPair.getDestConnId().getBytes()).serialize()); + buffer.put(FixedLengthPrecededBytes.of(this.connectionIdPair.getSrcConnId().getBytes()).serialize()); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + public ConnectionId getSrcConnId() { + return this.connectionIdPair.getSrcConnId(); + } + + public ConnectionId getDestConnId() { + return this.connectionIdPair.getDestConnId(); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/packet/longheader/LongHeaderPnSpacePacket.java b/src/main/java/core/packetproxy/quic/value/packet/longheader/LongHeaderPnSpacePacket.java new file mode 100644 index 0000000..75fc260 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/longheader/LongHeaderPnSpacePacket.java @@ -0,0 +1,187 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.packet.longheader; + +import lombok.EqualsAndHashCode; +import lombok.SneakyThrows; +import lombok.Value; +import lombok.experimental.NonFinal; +import org.apache.commons.lang3.ArrayUtils; +import packetproxy.quic.utils.Constants.PnSpaceType; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.value.TruncatedPacketNumber; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.value.key.Key; +import packetproxy.quic.value.packet.PnSpacePacket; + +import java.nio.ByteBuffer; +import java.util.Optional; + +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceInitial; + +/* +https://www.rfc-editor.org/rfc/rfc9000.html#name-long-header-packets + +Long Header Packet { + Header Form (1) = 1, + Fixed Bit (1) = 1, + Long Packet Type (2), + Type-Specific Bits (4), + Version (32), + Destination Connection ID Length (8), + Destination Connection ID (0..160), + Source Connection ID Length (8), + Source Connection ID (0..160), + Type-Specific Payload (..), +} +*/ +@EqualsAndHashCode(callSuper = true) +@NonFinal +@Value +public class LongHeaderPnSpacePacket extends LongHeaderPacket implements PnSpacePacket { + + protected PacketNumber packetNumber; + protected byte[] payload; + + protected LongHeaderPnSpacePacket(byte type, int version, ConnectionIdPair connIdPair, PacketNumber packetNumber, byte[] payload) { + super(type, version, connIdPair); + this.packetNumber = packetNumber; + this.payload = payload; + } + + protected LongHeaderPnSpacePacket(ByteBuffer buffer, Key key, PacketNumber largestAckedPn) throws Exception { + super(buffer); + int startPosition = buffer.position() - super.size(); + this.parseExtra(buffer); + + long length = VariableLengthInteger.parse(buffer).getValue(); + + // get the sampling data + int packetNumberPosition = buffer.position(); + buffer.position(buffer.position() + 4); + byte[] sample = SimpleBytes.parse(buffer, 16).getBytes(); + + // get the maskKey from the sampling data + byte[] maskKey = key.getMaskForHeaderProtection(sample); + + super.unmaskType(PacketHeaderType.LongHeaderType, maskKey); + int packetNumberLength = super.getOrigPnLength(); + + // decode header protection of truncatedPacketNumber + buffer.position(packetNumberPosition); + byte[] maskedTruncatedPn = SimpleBytes.parse(buffer, packetNumberLength).getBytes(); + byte[] truncatedPn = TruncatedPacketNumber.unmaskTruncatedPacketNumber(maskedTruncatedPn, maskKey); + + int payloadPosition = buffer.position(); + int payloadLength = (int) length - packetNumberLength; + byte[] encodedPayload = SimpleBytes.parse(buffer, payloadLength).getBytes(); + int positionPacketEnd = buffer.position(); + + buffer.position(startPosition); + byte[] header = SimpleBytes.parse(buffer, payloadPosition - startPosition).getBytes(); + header[0] = super.getType(); + for (int i = 0; i < truncatedPn.length; i++) { + header[packetNumberPosition - startPosition + i] = truncatedPn[i]; + } + + this.payload = key.decryptPayload(truncatedPn, encodedPayload, header); + this.packetNumber = new TruncatedPacketNumber(truncatedPn).getPacketNumber(largestAckedPn); + + buffer.position(positionPacketEnd); + } + + @SneakyThrows + public byte[] getBytes(Key key, PacketNumber largestAckedPn) { + ByteBuffer headerBuffer = ByteBuffer.allocate(1500); + + byte[] truncatedPn = this.packetNumber.getTruncatedPacketNumber(largestAckedPn).getBytes(); + if (truncatedPn.length + this.payload.length + 16 /* AES auth hash */ < 20) { + int dummyBytesLength = 20 - truncatedPn.length - this.payload.length - 16; + truncatedPn = ArrayUtils.addAll(new byte[dummyBytesLength], truncatedPn); + } + + byte[] payloadLength = VariableLengthInteger.of(truncatedPn.length + payload.length + 16 /* GCM auth hash */).getBytes(); + + /* create original header for associated data of AES encryption */ + headerBuffer.put(super.getBytes(truncatedPn.length)); + this.getBytesExtra(headerBuffer); + headerBuffer.put(payloadLength); + headerBuffer.put(truncatedPn); + headerBuffer.flip(); + byte[] header = SimpleBytes.parse(headerBuffer, headerBuffer.remaining()).getBytes(); + + byte[] encryptedPayload = key.encryptPayload(truncatedPn, payload, header); + byte[] sample = ArrayUtils.subarray(ArrayUtils.addAll(truncatedPn, encryptedPayload), 4, 4+16); + byte[] maskKey = key.getMaskForHeaderProtection(sample); + byte[] maskedTruncatedPn = TruncatedPacketNumber.maskTruncatedPacketNumber(truncatedPn, maskKey); + + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(super.getMaskedBytes(truncatedPn.length, maskKey)); + this.getBytesExtra(buffer); + buffer.put(payloadLength); + buffer.put(maskedTruncatedPn); + buffer.put(encryptedPayload); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + @Override + @SneakyThrows + public Frames getFrames() { + return Frames.parse(this.payload); + } + + @Override + @SneakyThrows + public boolean isAckEliciting() { + return Frames.parse(this.payload).isAckEliciting(); + } + + @Override + @SneakyThrows + public boolean hasAckFrame() { + return Frames.parse(this.payload).hasAckFrame(); + } + + @Override + @SneakyThrows + public Optional getAckFrame() { + return Frames.parse(this.payload).getAckFrame(); + } + + @Override + public PnSpaceType getPnSpaceType() { + return PnSpaceInitial; + } + + @Override + public String toString() { + return String.format("LongHeaderPacket(version=%d, connIdPair=%s, packetNumber=%s, payload=%s", + this.version, + this.connectionIdPair, + this.packetNumber, + Frames.parse(this.payload)); + } + + protected void parseExtra(ByteBuffer buffer) {}; + protected void getBytesExtra(ByteBuffer buffer) throws Exception {}; + +} diff --git a/src/main/java/core/packetproxy/quic/value/packet/longheader/RetryPacket.java b/src/main/java/core/packetproxy/quic/value/packet/longheader/RetryPacket.java new file mode 100644 index 0000000..f66f812 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/longheader/RetryPacket.java @@ -0,0 +1,68 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.packet.longheader; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; + +/* +https://datatracker.ietf.org/doc/html/rfc9000#section-17.2.5 +Retry Packet { + Header Form (1) = 1, + Fixed Bit (1) = 1, + Long Packet Type (2) = 3, + Unused (4), + Version (32), + Destination Connection ID Length (8), + Destination Connection ID (0..160), + Source Connection ID Length (8), + Source Connection ID (0..160), + Retry Token (..), + Retry Integrity Tag (128), +} +*/ +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class RetryPacket extends LongHeaderPacket { + + static public boolean is(byte type) { + return (type & 0xf0) == 0xf0; + } + + byte[] token; + byte[] tag; + + public RetryPacket(ByteBuffer buffer) { + super(buffer); + long length = VariableLengthInteger.parse(buffer).getValue(); + this.token = SimpleBytes.parse(buffer, length).getBytes(); + this.tag = SimpleBytes.parse(buffer, 16).getBytes(); + } + + public RetryPacket(byte type, int version, ConnectionIdPair connIdPair, byte[] token, byte[] tag) { + super(type, version, connIdPair); + this.token = token; + this.tag = tag; + } +} diff --git a/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/HandshakePacket.java b/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/HandshakePacket.java new file mode 100644 index 0000000..604693f --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/HandshakePacket.java @@ -0,0 +1,58 @@ +package packetproxy.quic.value.packet.longheader.pnspace; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.key.Key; +import packetproxy.quic.value.packet.longheader.LongHeaderPnSpacePacket; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.utils.Constants; + +import java.nio.ByteBuffer; + +/* Ref: RFC 9000 +Handshake Packet { + Header Form (1) = 1, + Fixed Bit (1) = 1, + Long Packet Type (2) = 2, + Reserved Bits (2), + Packet Number Length (2), + Version (32), + Destination Connection ID Length (8), + Destination Connection ID (0..160), + Source Connection ID Length (8), + Source Connection ID (0..160), + Length (i), + Packet Number (8..32), + Packet Payload (8..), +} +*/ +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class HandshakePacket extends LongHeaderPnSpacePacket { + + static public final byte TYPE = (byte)0xe0; + + static public boolean is(byte type) { + return (type & (byte)0xf0) == TYPE; + } + + static public HandshakePacket of(int version, ConnectionIdPair connIdPair, PacketNumber packetNumber, byte[] payload) { + return new HandshakePacket(TYPE, version, connIdPair, packetNumber, payload); + } + + public HandshakePacket(byte type, int version, ConnectionIdPair connIdPair, PacketNumber packetNumber, byte[] payload) { + super(type, version, connIdPair, packetNumber, payload); + } + + public HandshakePacket(ByteBuffer buffer, Key key, PacketNumber largestAckedPn) throws Exception { + super(buffer, key, largestAckedPn); + } + + @Override + public Constants.PnSpaceType getPnSpaceType() { + return Constants.PnSpaceType.PnSpaceHandshake; + } +} diff --git a/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/InitialPacket.java b/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/InitialPacket.java new file mode 100644 index 0000000..cf49f8f --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/InitialPacket.java @@ -0,0 +1,83 @@ +package packetproxy.quic.value.packet.longheader.pnspace; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import lombok.experimental.NonFinal; +import org.apache.commons.codec.binary.Hex; +import packetproxy.quic.value.key.Key; +import packetproxy.quic.value.packet.longheader.LongHeaderPnSpacePacket; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.value.VariableLengthPrecededBytes; +import packetproxy.quic.utils.Constants; + +import java.nio.ByteBuffer; + +/* Ref: RFC 9000 +Initial Packet { + Header Form (1) = 1, + Fixed Bit (1) = 1, + Long Packet Type (2) = 0, + Reserved Bits (2), + Packet Number Length (2), + Version (32), + Destination Connection ID Length (8), + Destination Connection ID (0..160), + Source Connection ID Length (8), + Source Connection ID (0..160), + Token Length (i), + Token (..), + Length (i), + Packet Number (8..32), + Packet Payload (8..), +} +*/ +@EqualsAndHashCode(callSuper = true) +@Value +public class InitialPacket extends LongHeaderPnSpacePacket { + + static public final byte TYPE = (byte)0xc0; + + static public boolean is(byte type) { + return (type & (byte)0xf0) == TYPE; + } + + static public InitialPacket of(int version, ConnectionIdPair connIdPair, PacketNumber packetNumber, byte[] payload, byte[] token) { + return new InitialPacket(TYPE, version, connIdPair, packetNumber, payload, token); + } + + @NonFinal + byte[] token; + + public InitialPacket(byte type, int version, ConnectionIdPair connIdPair, PacketNumber packetNumber, byte[] payload, byte[] token) { + super(type, version, connIdPair, packetNumber, payload); + this.token = token; + } + + public InitialPacket(ByteBuffer buffer, Key key, PacketNumber largestAckedPn) throws Exception { + super(buffer, key, largestAckedPn); + } + + @Override + protected void parseExtra(ByteBuffer buffer) { + this.token = VariableLengthPrecededBytes.parse(buffer).getBytes(); + } + + @Override + protected void getBytesExtra(ByteBuffer buffer) { + buffer.put(VariableLengthPrecededBytes.of(token).serialize()); + } + + @Override + public Constants.PnSpaceType getPnSpaceType() { + return Constants.PnSpaceType.PnSpaceInitial; + } + + @Override + public String toString() { + return String.format("InitialPacket(token=[%s], super=%s", + Hex.encodeHexString(this.token), + super.toString()); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/ZeroRttPacket.java b/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/ZeroRttPacket.java new file mode 100644 index 0000000..1fea380 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/longheader/pnspace/ZeroRttPacket.java @@ -0,0 +1,60 @@ +package packetproxy.quic.value.packet.longheader.pnspace; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.utils.Constants.PnSpaceType; +import packetproxy.quic.value.key.Key; +import packetproxy.quic.value.packet.longheader.LongHeaderPnSpacePacket; +import packetproxy.quic.value.ConnectionIdPair; +import packetproxy.quic.value.PacketNumber; + +import java.nio.ByteBuffer; + +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceApplicationData; + +/* Ref: RFC 9000 (0RTT Packet) +0-RTT Packet { + Header Form (1) = 1, + Fixed Bit (1) = 1, + Long Packet Type (2) = 1, + Reserved Bits (2), + Packet Number Length (2), + Version (32), + Destination Connection ID Length (8), + Destination Connection ID (0..160), + Source Connection ID Length (8), + Source Connection ID (0..160), + Length (i), + Packet Number (8..32), + Packet Payload (8..), +} +*/ +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class ZeroRttPacket extends LongHeaderPnSpacePacket { + + static public final byte TYPE = (byte)0xd0; + + static public boolean is(byte type) { + return (type & (byte)0xf0) == TYPE; + } + + static public ZeroRttPacket of(int version, ConnectionIdPair connIdPair, PacketNumber packetNumber, byte[] payload) { + return new ZeroRttPacket(TYPE, version, connIdPair, packetNumber, payload); + } + + public ZeroRttPacket(byte type, int version, ConnectionIdPair connIdPair, PacketNumber packetNumber, byte[] payload) { + super(type, version, connIdPair, packetNumber, payload); + } + + public ZeroRttPacket(ByteBuffer buffer, Key key, PacketNumber largestAckedPn) throws Exception { + super(buffer, key, largestAckedPn); + } + + @Override + public PnSpaceType getPnSpaceType() { + return PnSpaceApplicationData; + } +} diff --git a/src/main/java/core/packetproxy/quic/value/packet/shortheader/ShortHeaderPacket.java b/src/main/java/core/packetproxy/quic/value/packet/shortheader/ShortHeaderPacket.java new file mode 100644 index 0000000..b9fb9b1 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/packet/shortheader/ShortHeaderPacket.java @@ -0,0 +1,187 @@ +package packetproxy.quic.value.packet.shortheader; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.apache.commons.lang3.ArrayUtils; +import packetproxy.quic.utils.Constants; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.value.key.Key; +import packetproxy.quic.value.packet.PnSpacePacket; +import packetproxy.quic.value.packet.QuicPacket; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.TruncatedPacketNumber; + +import java.nio.ByteBuffer; +import java.util.Optional; + +/* +https://datatracker.ietf.org/doc/html/rfc9000#section-17.3 +1-RTT Packet { + Header Form (1) = 0, + Fixed Bit (1) = 1, + Spin Bit (1), + Reserved Bits (2), + Key Phase (1), + Packet Number Length (2), + Destination Connection ID (0..160), + Packet Number (8..32), + Packet Payload (8..), +} +*/ +@EqualsAndHashCode(callSuper = true) +@Value +public class ShortHeaderPacket extends QuicPacket implements PnSpacePacket { + + static public final byte TYPE = (byte)0x40; + + static public boolean is(byte type) { + return (type & (byte)0xc0) == TYPE; + } + + static public ConnectionId getDestConnId(ByteBuffer buffer) { + int savedPosition = buffer.position(); + buffer.get(); + byte[] destConnId = SimpleBytes.parse(buffer, Constants.CONNECTION_ID_SIZE).getBytes(); + buffer.position(savedPosition); + return ConnectionId.of(destConnId); + } + + static public ShortHeaderPacket of(ConnectionId destConnId, PacketNumber packetNumber, byte[] payload) { + return new ShortHeaderPacket(TYPE, destConnId, packetNumber, payload); + } + + ConnectionId destConnId; + PacketNumber packetNumber; + byte[] payload; + + public ShortHeaderPacket(byte type, ConnectionId destConnId, PacketNumber packetNumber, byte[] payload) { + super(type); + this.destConnId = destConnId; + this.packetNumber = packetNumber; + this.payload = payload; + } + + public ShortHeaderPacket(ByteBuffer buffer, Key key, PacketNumber largestAckedPn) throws Exception { + super(buffer); + int startPosition = buffer.position() - super.size(); + + this.destConnId = ConnectionId.parse(buffer, Constants.CONNECTION_ID_SIZE); + + // get the sampling data + int packetNumberPosition = buffer.position(); + buffer.position(buffer.position() + 4); + byte[] sample = SimpleBytes.parse(buffer, 16).getBytes(); + + // get the maskKey from the sampling data + byte[] maskKey = key.getMaskForHeaderProtection(sample); + + super.unmaskType(PacketHeaderType.ShortHeaderType, maskKey); + int packetNumberLength = super.getOrigPnLength(); + + // decode header protection of truncatedPacketNumber + buffer.position(packetNumberPosition); + byte[] maskedTruncatedPn = SimpleBytes.parse(buffer, packetNumberLength).getBytes(); + byte[] truncatedPn = TruncatedPacketNumber.unmaskTruncatedPacketNumber(maskedTruncatedPn, maskKey); + + int payloadPosition = buffer.position(); + int payloadLength = buffer.limit() - payloadPosition; + byte[] encodedPayload = SimpleBytes.parse(buffer, payloadLength).getBytes(); + int positionPacketEnd = buffer.position(); + + /* create unmasked header for associated data of AES decryption */ + buffer.position(startPosition); + byte[] header = SimpleBytes.parse(buffer, payloadPosition - startPosition).getBytes(); + header[0] = super.getType(); + for (int i = 0; i < truncatedPn.length; i++) { + header[packetNumberPosition - startPosition + i] = truncatedPn[i]; + } + + this.payload = key.decryptPayload(truncatedPn, encodedPayload, header); + this.packetNumber = new TruncatedPacketNumber(truncatedPn).getPacketNumber(largestAckedPn); + + buffer.position(positionPacketEnd); + } + + public byte[] getBytes(Key key, PacketNumber largestAckedPn) throws Exception { + ByteBuffer headerBuffer = ByteBuffer.allocate(1500); + + byte[] truncatedPn = this.packetNumber.getTruncatedPacketNumber(largestAckedPn).getBytes(); + if (truncatedPn.length + this.payload.length + 16 /* AES auth hash */ < 20) { + int dummyBytesLength = 20 - truncatedPn.length - this.payload.length - 16; + truncatedPn = ArrayUtils.addAll(new byte[dummyBytesLength], truncatedPn); + } + + /* create original header for associated data of AES encryption */ + byte type = super.getType(truncatedPn.length); + headerBuffer.put(type); + headerBuffer.put(this.destConnId.getBytes()); + headerBuffer.put(truncatedPn); + headerBuffer.flip(); + byte[] header = SimpleBytes.parse(headerBuffer, headerBuffer.remaining()).getBytes(); + + byte[] encryptedPayload = key.encryptPayload(truncatedPn, payload, header); + byte[] sample = ArrayUtils.subarray(ArrayUtils.addAll(truncatedPn, encryptedPayload), 4, 4+16); + byte[] maskKey = key.getMaskForHeaderProtection(sample); + byte maskedType = super.getMaskedType(truncatedPn.length, PacketHeaderType.ShortHeaderType, maskKey); + byte[] maskedTruncatedPn = TruncatedPacketNumber.maskTruncatedPacketNumber(truncatedPn, maskKey); + + ByteBuffer buffer = ByteBuffer.allocate(1500); + buffer.put(maskedType); + buffer.put(this.destConnId.getBytes()); + buffer.put(maskedTruncatedPn); + buffer.put(encryptedPayload); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } + + public int size() { + return this.getBytes().length; + } + + @Override + public String toString() { + try { + return String.format("ShortHeaderPacket(connIdPair=%s, packetNumber=%s, payload=%s", + this.destConnId, + this.packetNumber, + Frames.parse(this.payload)); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + } + + @Override + public PacketNumber getPacketNumber() { + return this.packetNumber; + } + + @Override + public boolean isAckEliciting() { + return Frames.parse(this.payload).isAckEliciting(); + } + + @Override + public boolean hasAckFrame() { + return Frames.parse(this.payload).hasAckFrame(); + } + + @Override + public Optional getAckFrame() { + return Frames.parse(this.payload).getAckFrame(); + } + + @Override + public Constants.PnSpaceType getPnSpaceType() { + return Constants.PnSpaceType.PnSpaceApplicationData; + } + + @Override + public Frames getFrames() { + Frames frames = Frames.parse(this.payload); + return frames; + } +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/TransportParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/TransportParameter.java new file mode 100644 index 0000000..28fdd0e --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/TransportParameter.java @@ -0,0 +1,74 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter; + +import lombok.*; +import lombok.experimental.NonFinal; +import packetproxy.quic.value.SimpleBytes; +import packetproxy.quic.value.VariableLengthInteger; + +import java.nio.ByteBuffer; + +/* +https://www.rfc-editor.org/rfc/rfc9000.html#transport-parameter-encoding + +Transport Parameter { + Transport Parameter ID (i), + Transport Parameter Length (i), + Transport Parameter Value (..), +} + +// Old Transport Parameters +ID: Description +====================== +0x0020: datagram +0x0040: multi path +0x1057: loss bits +0x173e: discard +0x2ab2: grease quic bit // https://datatracker.ietf.org/doc/html/draft-ietf-quic-bit-grease +0x7157: timestamp // https://datatracker.ietf.org/doc/html/draft-huitema-quic-ts-02#section-5 +0x7158: timestamp // https://datatracker.ietf.org/doc/html/draft-huitema-quic-ts-05#section-5 +0x73db: version negotiation // https://datatracker.ietf.org/doc/html/draft-ietf-quic-version-negotiation-03#section-12.1 +0xff73db: version negotiation // https://datatracker.ietf.org/doc/html/draft-ietf-quic-version-negotiation-08 +0xde1a: min ack delay // https://datatracker.ietf.org/doc/html/draft-iyengar-quic-delayed-ack-01#section-3 +0xff02de1a: min ack delay // https://datatracker.ietf.org/doc/html/draft-iyengar-quic-delayed-ack-02#section-3 +0xff03de1a: min ack delay // https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency +*/ + +@NonFinal +@AllArgsConstructor +@Value +public abstract class TransportParameter { + protected long parameterId; + protected long parameterLength; + protected byte[] parameterValue; + + public TransportParameter(ByteBuffer buffer) { + this.parameterId = VariableLengthInteger.parse(buffer).getValue(); + this.parameterLength = VariableLengthInteger.parse(buffer).getValue(); + this.parameterValue = SimpleBytes.parse(buffer, this.parameterLength).getBytes(); + } + + public byte[] getBytes() { + ByteBuffer buffer = ByteBuffer.allocate(4096); + buffer.put(VariableLengthInteger.of(this.parameterId).getBytes()); + buffer.put(VariableLengthInteger.of(this.parameterLength).getBytes()); + buffer.put(this.parameterValue); + buffer.flip(); + return SimpleBytes.parse(buffer, buffer.remaining()).getBytes(); + } +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/UnknownParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/UnknownParameter.java new file mode 100644 index 0000000..74bc38b --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/UnknownParameter.java @@ -0,0 +1,42 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class UnknownParameter extends TransportParameter { + static public final long ID = 0xdeadbeefL; + + public UnknownParameter(ByteBuffer buffer) { + super(buffer); + } + + public UnknownParameter(byte[] unknownBytes) { + super(ID, unknownBytes.length, unknownBytes); + } + public byte[] getValue() { + return super.parameterValue; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/bool/DisableActiveMigrationParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/bool/DisableActiveMigrationParameter.java new file mode 100644 index 0000000..b4b6685 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/bool/DisableActiveMigrationParameter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter.bool; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class DisableActiveMigrationParameter extends TransportParameter { + static public final long ID = 0xc; + + public DisableActiveMigrationParameter(ByteBuffer buffer) { + super(buffer); + } + + public DisableActiveMigrationParameter() { + super(ID, 0, new byte[0]); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/bool/ExpGreaseQuicBitParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/bool/ExpGreaseQuicBitParameter.java new file mode 100644 index 0000000..278697e --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/bool/ExpGreaseQuicBitParameter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter.bool; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class ExpGreaseQuicBitParameter extends TransportParameter { + static public final long ID = 0x2ab2; + + public ExpGreaseQuicBitParameter(ByteBuffer buffer) { + super(buffer); + } + + public ExpGreaseQuicBitParameter() { + super(ID, 0, new byte[0]); + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/InitSrcConnIdParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/InitSrcConnIdParameter.java new file mode 100644 index 0000000..0e483e4 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/InitSrcConnIdParameter.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter.bytearray; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class InitSrcConnIdParameter extends TransportParameter { + static public final long ID = 0xf; + + public InitSrcConnIdParameter(ByteBuffer buffer) { + super(buffer); + } + + public InitSrcConnIdParameter(byte[] srcConnId) { + super(ID, srcConnId.length, srcConnId); + } + + public byte[] getValue() { + return super.parameterValue; + } +} \ No newline at end of file diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/OrigDestConnIdParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/OrigDestConnIdParameter.java new file mode 100644 index 0000000..02df834 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/OrigDestConnIdParameter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter.bytearray; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class OrigDestConnIdParameter extends TransportParameter { + static public final long ID = 0x0; + + public OrigDestConnIdParameter(ByteBuffer buffer) { + super(buffer); + } + + public OrigDestConnIdParameter(byte[] destConnId) { + super(ID, destConnId.length, destConnId); + } + + public byte[] getValue() { + return super.parameterValue; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/RetrySrcConnIdParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/RetrySrcConnIdParameter.java new file mode 100644 index 0000000..0b1e887 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/RetrySrcConnIdParameter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter.bytearray; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class RetrySrcConnIdParameter extends TransportParameter { + static public final long ID = 0x10; + + public RetrySrcConnIdParameter(ByteBuffer buffer) { + super(buffer); + } + + public RetrySrcConnIdParameter(byte[] srcConnId) { + super(ID, srcConnId.length, srcConnId); + } + + public byte[] getValue() { + return super.parameterValue; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/StatelessResetTokenParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/StatelessResetTokenParameter.java new file mode 100644 index 0000000..18682d4 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/bytearray/StatelessResetTokenParameter.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter.bytearray; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class StatelessResetTokenParameter extends TransportParameter { + static public final long ID = 0x2; + + public StatelessResetTokenParameter(ByteBuffer buffer) { + super(buffer); + } + + public StatelessResetTokenParameter(byte[] token) { + super(ID, token.length, token); + } + + public byte[] getValue() { + return super.parameterValue; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/complex/PreferredAddressParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/complex/PreferredAddressParameter.java new file mode 100644 index 0000000..98f66b9 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/complex/PreferredAddressParameter.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.transportparameter.complex; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class PreferredAddressParameter extends TransportParameter { + static public final long ID = 0xd; + + public PreferredAddressParameter(ByteBuffer buffer) { + super(buffer); + } + + public byte[] getValue() { + return super.parameterValue; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/AckDelayExponentParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/AckDelayExponentParameter.java new file mode 100644 index 0000000..333b54c --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/AckDelayExponentParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class AckDelayExponentParameter extends TransportParameter { + static public final long ID = 0xa; + long value; + + public AckDelayExponentParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public AckDelayExponentParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/ActiveConnIdLimitParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/ActiveConnIdLimitParameter.java new file mode 100644 index 0000000..adf4983 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/ActiveConnIdLimitParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class ActiveConnIdLimitParameter extends TransportParameter { + static public final long ID = 0xe; + long value; + + public ActiveConnIdLimitParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public ActiveConnIdLimitParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/ExpMinAckDelayParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/ExpMinAckDelayParameter.java new file mode 100644 index 0000000..46fa973 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/ExpMinAckDelayParameter.java @@ -0,0 +1,29 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +/* https://datatracker.ietf.org/doc/html/draft-ietf-quic-ack-frequency */ +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class ExpMinAckDelayParameter extends TransportParameter { + static public final long ID = 0xff03de1aL; + long value; + + public ExpMinAckDelayParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public ExpMinAckDelayParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxDataParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxDataParameter.java new file mode 100644 index 0000000..3123285 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxDataParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class InitMaxDataParameter extends TransportParameter { + static public final long ID = 0x4; + long value; + + public InitMaxDataParameter(ByteBuffer buffer) { + super(buffer); + this.value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public InitMaxDataParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamBidiParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamBidiParameter.java new file mode 100644 index 0000000..6b1a155 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamBidiParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class InitMaxStreamBidiParameter extends TransportParameter { + static public final long ID = 0x8; + long value; + + public InitMaxStreamBidiParameter(ByteBuffer buffer) { + super(buffer); + this.value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public InitMaxStreamBidiParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataBidiLocalParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataBidiLocalParameter.java new file mode 100644 index 0000000..fb8d164 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataBidiLocalParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class InitMaxStreamDataBidiLocalParameter extends TransportParameter { + static public final long ID = 0x5; + long value; + + public InitMaxStreamDataBidiLocalParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public InitMaxStreamDataBidiLocalParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataBidiRemoteParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataBidiRemoteParameter.java new file mode 100644 index 0000000..29b5afe --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataBidiRemoteParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class InitMaxStreamDataBidiRemoteParameter extends TransportParameter { + static public final long ID = 0x6; + long value; + + public InitMaxStreamDataBidiRemoteParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public InitMaxStreamDataBidiRemoteParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataUniParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataUniParameter.java new file mode 100644 index 0000000..0047d6e --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamDataUniParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class InitMaxStreamDataUniParameter extends TransportParameter { + static public final long ID = 0x7; + long value; + + public InitMaxStreamDataUniParameter(ByteBuffer buffer) { + super(buffer); + this.value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public InitMaxStreamDataUniParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamUniParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamUniParameter.java new file mode 100644 index 0000000..cf7e823 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/InitMaxStreamUniParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class InitMaxStreamUniParameter extends TransportParameter { + static public final long ID = 0x9; + long value; + + public InitMaxStreamUniParameter(ByteBuffer buffer) { + super(buffer); + this.value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public InitMaxStreamUniParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxAckDelayParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxAckDelayParameter.java new file mode 100644 index 0000000..06deb88 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxAckDelayParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class MaxAckDelayParameter extends TransportParameter { + static public final long ID = 0xb; + long value; + + public MaxAckDelayParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public MaxAckDelayParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxIdleTimeoutParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxIdleTimeoutParameter.java new file mode 100644 index 0000000..df436f3 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxIdleTimeoutParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class MaxIdleTimeoutParameter extends TransportParameter { + static public final long ID = 0x1; + long value; + + public MaxIdleTimeoutParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public MaxIdleTimeoutParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxUdpPayloadSizeParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxUdpPayloadSizeParameter.java new file mode 100644 index 0000000..8be02e4 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/MaxUdpPayloadSizeParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class MaxUdpPayloadSizeParameter extends TransportParameter { + static public final long ID = 0x3; + long value; + + public MaxUdpPayloadSizeParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public MaxUdpPayloadSizeParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/OldMinAckDelayParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/OldMinAckDelayParameter.java new file mode 100644 index 0000000..d9a6961 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/OldMinAckDelayParameter.java @@ -0,0 +1,30 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +/* https://datatracker.ietf.org/doc/html/draft-iyengar-quic-delayed-ack-02#section-3 */ +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class OldMinAckDelayParameter extends TransportParameter { + static public final long ID = 0xff02de1aL; + + long value; + + public OldMinAckDelayParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public OldMinAckDelayParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/quic/value/transportparameter/number/OldTimestampParameter.java b/src/main/java/core/packetproxy/quic/value/transportparameter/number/OldTimestampParameter.java new file mode 100644 index 0000000..1c78049 --- /dev/null +++ b/src/main/java/core/packetproxy/quic/value/transportparameter/number/OldTimestampParameter.java @@ -0,0 +1,28 @@ +package packetproxy.quic.value.transportparameter.number; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.value.VariableLengthInteger; +import packetproxy.quic.value.transportparameter.TransportParameter; + +import java.nio.ByteBuffer; + +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Value +public class OldTimestampParameter extends TransportParameter { + static public final long ID = 0x7158; + long value; + + public OldTimestampParameter(ByteBuffer buffer) { + super(buffer); + value = VariableLengthInteger.parse(super.parameterValue).getValue(); + } + + public OldTimestampParameter(long value) { + super(ID, VariableLengthInteger.of(value).getBytes().length, VariableLengthInteger.of(value).getBytes()); + this.value = value; + } + +} diff --git a/src/main/java/core/packetproxy/util/Throwing.java b/src/main/java/core/packetproxy/util/Throwing.java new file mode 100644 index 0000000..67c739e --- /dev/null +++ b/src/main/java/core/packetproxy/util/Throwing.java @@ -0,0 +1,20 @@ +package packetproxy.util; + +import javax.annotation.Nonnull; +import java.util.function.Consumer; + +public class Throwing { + + private Throwing() {} + + @Nonnull + public static Consumer rethrow(@Nonnull final ThrowingConsumer consumer) { + return consumer; + } + + @SuppressWarnings("unchecked") + @Nonnull + public static void sneakyThrow(@Nonnull Throwable ex) throws E { + throw (E) ex; + } +} diff --git a/src/main/java/core/packetproxy/util/ThrowingConsumer.java b/src/main/java/core/packetproxy/util/ThrowingConsumer.java new file mode 100644 index 0000000..33c5e7f --- /dev/null +++ b/src/main/java/core/packetproxy/util/ThrowingConsumer.java @@ -0,0 +1,18 @@ +package packetproxy.util; + +import java.util.function.Consumer; + +@FunctionalInterface +public interface ThrowingConsumer extends Consumer { + + @Override + default void accept(final T e) { + try { + accept0(e); + } catch (Throwable ex) { + Throwing.sneakyThrow(ex); + } + } + + void accept0(T e) throws Throwable; +} \ No newline at end of file diff --git a/src/test/java/packetproxy/PrivateDNSClientTest.java b/src/test/java/packetproxy/PrivateDNSClientTest.java new file mode 100644 index 0000000..6b3432b --- /dev/null +++ b/src/test/java/packetproxy/PrivateDNSClientTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; + +import static org.junit.jupiter.api.Assertions.*; + +class PrivateDNSClientTest { + + @Test + public void 成功するケース() throws Exception { + ArrayList lines = new ArrayList<>(); + lines.add("127.0.0.1 aaa aaa.example.com # for test"); + assertTrue(PrivateDNSClient.dnsLoopingFromHostsLines(lines, "aaa.example.com")); + } + + @Test + public void 成功するケース2() throws Exception { + ArrayList lines = new ArrayList<>(); + lines.add("2.3.4.5 aaa bbb.example.com"); + lines.add("127.0.0.1 aaa aaa.example.com"); + assertTrue(PrivateDNSClient.dnsLoopingFromHostsLines(lines, "aaa.example.com")); + } + + @Test + public void 成功するケース3() throws Exception { + ArrayList lines = new ArrayList<>(); + lines.add(" # this is a comment."); + lines.add("# 2.3.4.5 aaa bbb.example.com"); + lines.add("127.0.0.1 aaa aaa.example.com"); + lines.add(" # 3.3.3.3 aaa ccc.example.com"); + assertTrue(PrivateDNSClient.dnsLoopingFromHostsLines(lines, "aaa.example.com")); + } + + @Test + public void 失敗するケース() throws Exception { + ArrayList lines = new ArrayList<>(); + lines.add("127.0.0.2 aaa aaa.example.com # for test"); + assertFalse(PrivateDNSClient.dnsLoopingFromHostsLines(lines, "aaa.example.com")); + } + + @Test + public void 失敗するケース2() throws Exception { + ArrayList lines = new ArrayList<>(); + lines.add("# 127.0.0.2 aaa aaa.example.com"); + assertFalse(PrivateDNSClient.dnsLoopingFromHostsLines(lines, "aaa.example.com")); + } + + @Test + public void 失敗するケース3() throws Exception { + ArrayList lines = new ArrayList<>(); + lines.add("# 127.0.0.2 aaa bbb.example.com"); + assertFalse(PrivateDNSClient.dnsLoopingFromHostsLines(lines, "aaa.example.com")); + } + + @Test + public void 失敗するケース4() throws Exception { + ArrayList lines = new ArrayList<>(); + lines.add(" # this is a comment."); + lines.add("# 2.3.4.5 aaa bbb.example.com"); + lines.add("127.0.0.1 aaa ddd.example.com"); + lines.add(" # 3.3.3.3 aaa ccc.example.com"); + assertFalse(PrivateDNSClient.dnsLoopingFromHostsLines(lines, "aaa.example.com")); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/http3/QpackTest.java b/src/test/java/packetproxy/http3/QpackTest.java new file mode 100644 index 0000000..d8e0569 --- /dev/null +++ b/src/test/java/packetproxy/http3/QpackTest.java @@ -0,0 +1,81 @@ +package packetproxy.http3; + +import org.apache.commons.codec.binary.Hex; +import org.eclipse.jetty.http.HttpFields; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http.MetaData; +import org.eclipse.jetty.http3.qpack.Instruction; +import org.eclipse.jetty.http3.qpack.QpackDecoder; +import org.eclipse.jetty.http3.qpack.QpackEncoder; +import org.eclipse.jetty.io.ByteBufferPool; +import org.eclipse.jetty.io.MappedByteBufferPool; +import org.junit.jupiter.api.Test; +import packetproxy.quic.value.SimpleBytes; + +import java.nio.ByteBuffer; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +public class QpackTest { + @Test + public void Encode後DeCodeできるかテスト() throws Exception { + + ByteBufferPool bufferPool = new MappedByteBufferPool(); + ByteBufferPool.Lease lease = new ByteBufferPool.Lease(bufferPool); + + ByteBufferPool bufferPool2 = new MappedByteBufferPool(); + ByteBufferPool.Lease lease2 = new ByteBufferPool.Lease(bufferPool2); + + QpackEncoder encoder = new QpackEncoder(new Instruction.Handler() { + @Override + public void onInstructions(List instructions) { + System.out.println("Encode Instructions: " + instructions); + instructions.stream().forEach(i -> i.encode(lease)); + } + }, 100); + + QpackDecoder decoder = new QpackDecoder(new Instruction.Handler() { + @Override + public void onInstructions(List instructions) { + System.out.println("Decode Instructions: " + instructions); + instructions.stream().forEach(i -> i.encode(lease2)); + } + }, 100); + + encoder.setCapacity(100); + ByteBuffer buffer = ByteBuffer.allocate(1024); + HttpFields httpFields = HttpFields.build().add("hoge", "fuga"); + encoder.encode(buffer, 0, new MetaData(HttpVersion.HTTP_3, httpFields)); + buffer.flip(); + System.out.println("Stream data: " + Hex.encodeHexString(SimpleBytes.parse(buffer, buffer.remaining()).getBytes())); + + lease.getByteBuffers().forEach(b -> { + try { + decoder.parseInstructions(b); + } catch (Exception e) { + e.printStackTrace(); + } + }); + + buffer.position(0); + decoder.decode(0, buffer, new QpackDecoder.Handler() { + @Override + public void onMetaData(long streamId, MetaData metadata) { + System.out.println("streamId: " + streamId + ", meta: " + metadata); + assertThat(httpFields.asImmutable()).isEqualTo(metadata.getFields()); + } + }); + + System.out.println(encoder.dump()); + lease2.getByteBuffers().forEach(b -> { + try { + encoder.parseInstructions(b); + } catch (Exception e) { + e.printStackTrace(); + } + }); + System.out.println(encoder.dump()); + + } +} diff --git a/src/test/java/packetproxy/quic/service/ackgenerator/AckFrameGeneratorTest.java b/src/test/java/packetproxy/quic/service/ackgenerator/AckFrameGeneratorTest.java new file mode 100644 index 0000000..881351b --- /dev/null +++ b/src/test/java/packetproxy/quic/service/ackgenerator/AckFrameGeneratorTest.java @@ -0,0 +1,123 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.ackgenerator; + + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.service.framegenerator.AckFrameGenerator; + +import static org.junit.jupiter.api.Assertions.*; + +class AckFrameGeneratorTest { + + @Test + public void 全て受信() throws Exception { + AckFrameGenerator ackFrameGenerator = new AckFrameGenerator(); + + ackFrameGenerator.received(0); + ackFrameGenerator.received(1); + ackFrameGenerator.received(2); + ackFrameGenerator.received(3); + + AckFrame ackFrame = ackFrameGenerator.generateAckFrame(); + assertEquals(3, ackFrame.getLargestAcknowledged()); + assertEquals(3, ackFrame.getFirstAckRange()); + assertEquals(0, ackFrame.getAckRangeCount()); + } + + @Test + public void 受信できていないpacketが存在() throws Exception { + AckFrameGenerator ackFrameGenerator = new AckFrameGenerator(); + + ackFrameGenerator.received(100); + + AckFrame ackFrame = ackFrameGenerator.generateAckFrame(); + assertEquals(100, ackFrame.getLargestAcknowledged()); + assertEquals(0, ackFrame.getFirstAckRange()); + assertEquals(0, ackFrame.getAckRangeCount()); + } + + @Test + public void 受信できていないpacketが複数存在() throws Exception { + AckFrameGenerator ackFrameGenerator = new AckFrameGenerator(); + + ackFrameGenerator.received(2); + ackFrameGenerator.received(3); + ackFrameGenerator.received(4); + ackFrameGenerator.received(5); + ackFrameGenerator.received(6); + ackFrameGenerator.received(7); + ackFrameGenerator.received(10); + + AckFrame ackFrame = ackFrameGenerator.generateAckFrame(); + assertEquals(10, ackFrame.getLargestAcknowledged()); + assertEquals(0, ackFrame.getFirstAckRange()); + assertEquals(1, ackFrame.getAckRangeCount()); + assertEquals(1, ackFrame.getAckRanges().get(0).getGap()); + assertEquals(5, ackFrame.getAckRanges().get(0).getAckRangeLength()); + } + + @Test + public void 最初のpacketを受信した後getBytesできること() throws Exception { + AckFrameGenerator ackFrameGenerator = new AckFrameGenerator(); + ackFrameGenerator.received(0); + AckFrame ackFrame = ackFrameGenerator.generateAckFrame(); + + assertEquals(0, ackFrame.getLargestAcknowledged()); + assertEquals(0, ackFrame.getFirstAckRange()); + assertEquals(0, ackFrame.getAckRangeCount()); + assertArrayEquals(Hex.decodeHex("0200000000".toCharArray()), ackFrame.getBytes()); + } + + @Test + public void 相手に受信されるとAckFrameは生成されない() throws Exception { + AckFrameGenerator ackFrameGenerator = new AckFrameGenerator(); + ackFrameGenerator.received(2); + ackFrameGenerator.received(4); + ackFrameGenerator.received(6); + AckFrame ackFrame = ackFrameGenerator.generateAckFrame(); + ackFrameGenerator.confirmedAckFrame(ackFrame); + AckFrame ackFrame2 = ackFrameGenerator.generateAckFrame(); + + assertNotNull(ackFrame); + assertNull(ackFrame2); + } + + @Test + public void 相手に受信された後さらに受信すると差分のAckFrameが生成される() throws Exception { + AckFrameGenerator ackFrameGenerator = new AckFrameGenerator(); + ackFrameGenerator.received(2); + ackFrameGenerator.received(4); + ackFrameGenerator.received(6); + AckFrame ackFrame = ackFrameGenerator.generateAckFrame(); + ackFrameGenerator.received(7); + ackFrameGenerator.received(9); + ackFrameGenerator.confirmedAckFrame(ackFrame); + AckFrame ackFrame2 = ackFrameGenerator.generateAckFrame(); + + assertNotNull(ackFrame); + assertNotNull(ackFrame2); + assertEquals(9, ackFrame2.getLargestAcknowledged()); + assertEquals(0, ackFrame2.getFirstAckRange()); + assertEquals(1, ackFrame2.getAckRangeCount()); + assertEquals(0, ackFrame2.getAckRanges().get(0).getGap()); + assertEquals(0, ackFrame2.getAckRanges().get(0).getAckRangeLength()); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/service/ackgenerator/ReceivedQuicPacketNumbersTest.java b/src/test/java/packetproxy/quic/service/ackgenerator/ReceivedQuicPacketNumbersTest.java new file mode 100644 index 0000000..e355de6 --- /dev/null +++ b/src/test/java/packetproxy/quic/service/ackgenerator/ReceivedQuicPacketNumbersTest.java @@ -0,0 +1,115 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.ackgenerator; + + +import org.junit.jupiter.api.Test; +import packetproxy.quic.service.framegenerator.helper.ReceivedPacketNumbers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ReceivedQuicPacketNumbersTest { + + @Test + public void smoke() throws Exception { + ReceivedPacketNumbers receivedPacketNumbers = new ReceivedPacketNumbers(); + receivedPacketNumbers.received(0); + receivedPacketNumbers.received(1); + receivedPacketNumbers.received(2); + receivedPacketNumbers.received(3); + receivedPacketNumbers.unreceived(4); + receivedPacketNumbers.unreceived(5); + receivedPacketNumbers.unreceived(6); + + assertEquals(7, receivedPacketNumbers.getSmallestOfRange(10, 0)); + assertEquals(4, receivedPacketNumbers.getSmallestOfGap(6, 0)); + assertEquals(0, receivedPacketNumbers.getSmallestOfRange(3, 0)); + } + + @Test + public void 渡した値がそのまま返るパターン() throws Exception { + ReceivedPacketNumbers receivedPacketNumbers = new ReceivedPacketNumbers(); + receivedPacketNumbers.unreceived(5); + + assertEquals(6, receivedPacketNumbers.getSmallestOfRange(6, 0)); + assertEquals(5, receivedPacketNumbers.getSmallestOfGap(5, 0)); + } + + @Test + public void 何も受信していない() throws Exception { + ReceivedPacketNumbers receivedPacketNumbers = new ReceivedPacketNumbers(); + receivedPacketNumbers.unreceived(0); + receivedPacketNumbers.unreceived(1); + receivedPacketNumbers.unreceived(2); + receivedPacketNumbers.unreceived(3); + + assertEquals(0, receivedPacketNumbers.getSmallestOfGap(3, 0)); + } + + @Test + public void 全て受信している() throws Exception { + ReceivedPacketNumbers receivedPacketNumbers = new ReceivedPacketNumbers(); + receivedPacketNumbers.received(0); + receivedPacketNumbers.received(1); + receivedPacketNumbers.received(2); + receivedPacketNumbers.received(3); + assertEquals(0, receivedPacketNumbers.getSmallestOfRange(3, 0)); + } + + @Test + public void smallestValidで限定している() throws Exception { + ReceivedPacketNumbers receivedPacketNumbers = new ReceivedPacketNumbers(); + receivedPacketNumbers.received(0); + receivedPacketNumbers.received(1); + receivedPacketNumbers.received(2); + receivedPacketNumbers.received(3); + receivedPacketNumbers.unreceived(4); + receivedPacketNumbers.unreceived(5); + receivedPacketNumbers.unreceived(6); + + assertEquals(7, receivedPacketNumbers.getSmallestOfRange(10, 2)); + assertEquals(4, receivedPacketNumbers.getSmallestOfGap(6, 2)); + assertEquals(5, receivedPacketNumbers.getSmallestOfGap(6, 5)); + assertEquals(2, receivedPacketNumbers.getSmallestOfRange(3, 2)); + assertEquals(3, receivedPacketNumbers.getSmallestOfRange(3, 3)); + } + + @Test + public void largestOfGapとsmallestValidが同じ() throws Exception { + ReceivedPacketNumbers receivedPacketNumbers = new ReceivedPacketNumbers(); + receivedPacketNumbers.unreceived(0); + receivedPacketNumbers.unreceived(1); + receivedPacketNumbers.received(2); + receivedPacketNumbers.received(3); + + assertEquals(1, receivedPacketNumbers.getSmallestOfGap(1, 1)); + assertEquals(0, receivedPacketNumbers.getSmallestOfGap(0, 0)); + } + + @Test + public void largestOfRangeとsmallestValidが同じ() throws Exception { + ReceivedPacketNumbers receivedPacketNumbers = new ReceivedPacketNumbers(); + receivedPacketNumbers.unreceived(0); + receivedPacketNumbers.unreceived(1); + receivedPacketNumbers.received(2); + receivedPacketNumbers.received(3); + + assertEquals(3, receivedPacketNumbers.getSmallestOfRange(3, 3)); + assertEquals(2, receivedPacketNumbers.getSmallestOfRange(2, 2)); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/service/framegenerator/CryptoFramesToMessagesTest.java b/src/test/java/packetproxy/quic/service/framegenerator/CryptoFramesToMessagesTest.java new file mode 100644 index 0000000..78450a2 --- /dev/null +++ b/src/test/java/packetproxy/quic/service/framegenerator/CryptoFramesToMessagesTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.framegenerator; + +import net.luminis.tls.handshake.ClientHello; +import net.luminis.tls.handshake.HandshakeMessage; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.lang3.ArrayUtils; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import packetproxy.quic.value.frame.CryptoFrame; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +class CryptoFramesToMessagesTest { + + private CryptoFramesToMessages stream; + private byte[] clientHelloBytes; + private HandshakeMessage clientHello; + + @BeforeEach + public void before() throws Exception { + this.stream = new CryptoFramesToMessages(); + this.clientHelloBytes = Hex.decodeHex("010000ed0303ebf8fa56f12939b9584a3896472ec40bb863cfd3e86804fe3a47f06a2b69484c00000413011302010000c000000010000e00000b6578616d706c652e636f6dff01000100000a00080006001d0017001800100007000504616c706e000500050100000000003300260024001d00209370b2c9caa47fbabaf4559fedba753de171fa71f50f1ce15d43e994ec74d748002b0003020304000d0010000e0403050306030203080408050806002d00020101001c00024001003900320408ffffffffffffffff05048000ffff07048000ffff0801100104800075300901100f088394c8f03e51570806048000ffff".toCharArray()); + this.clientHello = CryptoFramesToMessages.convertToHandshakeMessage(clientHelloBytes); + } + + @Test + public void 複数のHandshakeMessageを処理できる() { + this.stream.write(new CryptoFrame(0, this.clientHelloBytes)); + this.stream.write(new CryptoFrame(this.clientHelloBytes.length, this.clientHelloBytes)); + Optional retMsg1 = this.stream.getHandshakeMessage(); + Optional retMsg2 = this.stream.getHandshakeMessage(); + assertThat(retMsg1.get()).isInstanceOf(ClientHello.class); + assertThat(retMsg2.get()).isInstanceOf(ClientHello.class); + } + + @Test + public void 一つのHandshakeMessageが分割された状態でも処理できる() { + byte[] array1 = ArrayUtils.subarray(this.clientHelloBytes, 0, 10); + byte[] array2 = ArrayUtils.subarray(this.clientHelloBytes, 10, 50); + byte[] array3 = ArrayUtils.subarray(this.clientHelloBytes, 50, this.clientHelloBytes.length); + + CryptoFrame cryptoFrame1 = new CryptoFrame(0, array1); + CryptoFrame cryptoFrame2 = new CryptoFrame(10, array2); + CryptoFrame cryptoFrame3 = new CryptoFrame(50, array3); + + this.stream.write(cryptoFrame1); + this.stream.write(cryptoFrame2); + this.stream.write(cryptoFrame3); + + Optional ret = stream.getHandshakeMessage(); + assertThat(ret.get()).isInstanceOf(ClientHello.class); + } + + @Test + public void 一つのHandshakeMessageが分割されてシャフルされた状態でも処理できる() { + byte[] array1 = ArrayUtils.subarray(this.clientHelloBytes, 0, 10); + byte[] array2 = ArrayUtils.subarray(this.clientHelloBytes, 10, 50); + byte[] array3 = ArrayUtils.subarray(this.clientHelloBytes, 50, this.clientHelloBytes.length); + + CryptoFrame cryptoFrame1 = new CryptoFrame(0, array1); + CryptoFrame cryptoFrame2 = new CryptoFrame(10, array2); + CryptoFrame cryptoFrame3 = new CryptoFrame(50, array3); + + this.stream.write(cryptoFrame3); + this.stream.write(cryptoFrame1); + this.stream.write(cryptoFrame2); + + Optional ret = stream.getHandshakeMessage(); + assertThat(ret.get()).isInstanceOf(ClientHello.class); + } + + @Test + public void 複数のHandshakeMessageが分割されてシャフルされた状態でも処理できる() { + byte[] array10 = ArrayUtils.subarray(this.clientHelloBytes, 0, 10); + byte[] array40 = ArrayUtils.subarray(this.clientHelloBytes, 10, 50); + byte[] arrayRemaining = ArrayUtils.subarray(this.clientHelloBytes, 50, this.clientHelloBytes.length); + + CryptoFrame cryptoFrame1 = new CryptoFrame(0, array10); + CryptoFrame cryptoFrame2 = new CryptoFrame(10, array40); + CryptoFrame cryptoFrame3 = new CryptoFrame(50, arrayRemaining); + CryptoFrame cryptoFrame4 = new CryptoFrame(this.clientHelloBytes.length, array10); + CryptoFrame cryptoFrame5 = new CryptoFrame(this.clientHelloBytes.length + 10, array40); + CryptoFrame cryptoFrame6 = new CryptoFrame(this.clientHelloBytes.length + 50, arrayRemaining); + + this.stream.write(cryptoFrame4); + this.stream.write(cryptoFrame1); + this.stream.write(cryptoFrame2); + this.stream.write(cryptoFrame5); + this.stream.write(cryptoFrame6); + this.stream.write(cryptoFrame3); + + Optional ret1 = stream.getHandshakeMessage(); + Optional ret2 = stream.getHandshakeMessage(); + assertThat(ret1.get()).isInstanceOf(ClientHello.class); + assertThat(ret2.get()).isInstanceOf(ClientHello.class); + } + + @Test + public void 一つのCryptoFrameに複数のHandshakeMessageが入った状態でも処理できる() { + byte[] array10 = ArrayUtils.subarray(this.clientHelloBytes, 0, 10); + byte[] array40 = ArrayUtils.subarray(this.clientHelloBytes, 10, 50); + byte[] arrayRemaining = ArrayUtils.subarray(this.clientHelloBytes, 50, this.clientHelloBytes.length); + + CryptoFrame cryptoFrame1 = new CryptoFrame(0, array10); + CryptoFrame cryptoFrame2 = new CryptoFrame(10, array40); + CryptoFrame cryptoFrame3 = new CryptoFrame(50, ArrayUtils.addAll(arrayRemaining, array10)); /* 次のMessageが混じった状態 */ + CryptoFrame cryptoFrame4 = new CryptoFrame(this.clientHelloBytes.length + 10, array40); + CryptoFrame cryptoFrame5 = new CryptoFrame(this.clientHelloBytes.length + 50, arrayRemaining); + + this.stream.write(cryptoFrame4); + this.stream.write(cryptoFrame1); + this.stream.write(cryptoFrame2); + this.stream.write(cryptoFrame5); + this.stream.write(cryptoFrame3); + + Optional ret1 = stream.getHandshakeMessage(); + Optional ret2 = stream.getHandshakeMessage(); + assertThat(ret1.get()).isInstanceOf(ClientHello.class); + assertThat(ret2.get()).isInstanceOf(ClientHello.class); + } + + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/service/pnspace/SentPacketsTest.java b/src/test/java/packetproxy/quic/service/pnspace/SentPacketsTest.java new file mode 100644 index 0000000..27b723f --- /dev/null +++ b/src/test/java/packetproxy/quic/service/pnspace/SentPacketsTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.pnspace; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import packetproxy.quic.value.SentPacket; +import packetproxy.quic.service.pnspace.helper.SentPackets; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.value.frame.helper.AckRanges; +import packetproxy.quic.value.packet.helper.TestPacket; +import packetproxy.quic.value.PacketNumber; + +import java.util.Optional; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class SentPacketsTest { + + private SentPackets sentPackets; + + @BeforeEach + void beforeEach() throws Exception { + this.sentPackets = new SentPackets(); + } + + @Test + void getLargestAckFrameが動作すること() throws Exception { + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(0), new AckFrame(0, 0, 0, 0, AckRanges.emptyAckRanges)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(1)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(2), new AckFrame(2, 0, 0, 0, AckRanges.emptyAckRanges)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(3)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(4)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(5), new AckFrame(1, 0, 0, 0, AckRanges.emptyAckRanges)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(6)))); + + Optional a = sentPackets.getLargestAckFrame(); + assertThat(a).hasValue(new AckFrame(2, 0, 0, 0, AckRanges.emptyAckRanges)); + } + + @Test + void getLargestAckFrameがEmptyになること() throws Exception { + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(1)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(3)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(4)))); + this.sentPackets.add(new SentPacket(TestPacket.of(PacketNumber.of(6)))); + + assertThat(sentPackets.getLargestAckFrame()).isEqualTo(Optional.empty()); + } +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/service/transportparameter/TransportParametersTest.java b/src/test/java/packetproxy/quic/service/transportparameter/TransportParametersTest.java new file mode 100644 index 0000000..a4767b9 --- /dev/null +++ b/src/test/java/packetproxy/quic/service/transportparameter/TransportParametersTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.service.transportparameter; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; +import packetproxy.quic.utils.Constants; + +import static org.junit.jupiter.api.Assertions.assertEquals; + + +class TransportParametersTest { + + @Test + public void smoke() throws Exception { + byte[] test = Hex.decodeHex("0039005505048020000004048010000008024201010480007530030245a00902420106048001006307048000ffff0e01080b010a0f087c67f19599d5b680537b0480004fb0c0000000ff02de1a0243e88000715801036ab200".toCharArray()); + TransportParameters params = new TransportParameters(Constants.Role.CLIENT, test); + System.out.println(params); + assertEquals(2097152, params.getInitMaxStreamDataBidiLocal()); + assertEquals(1048576, params.getInitMaxData()); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/ConnectionIdTest.java b/src/test/java/packetproxy/quic/value/ConnectionIdTest.java new file mode 100644 index 0000000..509c056 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/ConnectionIdTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import packetproxy.quic.utils.Constants; + +import static org.junit.jupiter.api.Assertions.*; + +class ConnectionIdTest { + + @Test + public void ランダムなConnectionIdを生成できること() { + ConnectionId connId = ConnectionId.generateRandom(); + Assertions.assertEquals(Constants.CONNECTION_ID_SIZE, connId.getBytes().length); + } + + @Test + public void ByteArrayが同じならEqualになること() throws Exception { + byte[] connIdBytes = Hex.decodeHex("11223344".toCharArray()); + ConnectionId connId1 = ConnectionId.of(connIdBytes); + ConnectionId connId2= ConnectionId.of(connIdBytes); + assertEquals(connId1, connId2); + } + + @Test + public void 複数個のインスタンスがお互いにランダムになっていること() throws Exception { + ConnectionId connId1 = ConnectionId.generateRandom(); + ConnectionId connId2 = ConnectionId.generateRandom(); + assertNotEquals(connId1, connId2); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/QuicPacketNumbersTest.java b/src/test/java/packetproxy/quic/value/QuicPacketNumbersTest.java new file mode 100644 index 0000000..f7c0338 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/QuicPacketNumbersTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import org.junit.jupiter.api.Test; +import packetproxy.quic.utils.PacketNumbers; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class QuicPacketNumbersTest { + + @Test + void testLargest() throws Exception { + PacketNumbers pns = new PacketNumbers(); + pns.add(PacketNumber.of(100L)); + pns.add(PacketNumber.of(1L)); + pns.add(PacketNumber.of(33L)); + pns.add(PacketNumber.of(0x7fffffffffffffffL)); + + assertEquals(PacketNumber.of(0x7fffffffffffffffL), pns.largest()); + } +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/TruncatedQuicPacketNumberTest.java b/src/test/java/packetproxy/quic/value/TruncatedQuicPacketNumberTest.java new file mode 100644 index 0000000..c88703d --- /dev/null +++ b/src/test/java/packetproxy/quic/value/TruncatedQuicPacketNumberTest.java @@ -0,0 +1,120 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TruncatedQuicPacketNumberTest { + + @Test + public void _0() throws Exception { + PacketNumber packetNumber = PacketNumber.of(0); + PacketNumber largestAckedPn = PacketNumber.Infinite; + + TruncatedPacketNumber truncatedPn = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertEquals(packetNumber, truncatedPn.getPacketNumber(largestAckedPn)); + } + + @Test + public void _1() throws Exception { + PacketNumber packetNumber = PacketNumber.of(1); + PacketNumber largestAckedPn = PacketNumber.of(0); + + TruncatedPacketNumber truncatedPn = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertEquals(packetNumber, truncatedPn.getPacketNumber(largestAckedPn)); + } + + @Test + public void _255() throws Exception { + PacketNumber packetNumber = PacketNumber.of(255); + PacketNumber largestAckedPn = PacketNumber.of(0); + + TruncatedPacketNumber truncatedPn = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertEquals(packetNumber, truncatedPn.getPacketNumber(largestAckedPn)); + } + + @Test + public void _256() throws Exception { + PacketNumber packetNumber = PacketNumber.of(256); + PacketNumber largestAckedPn = PacketNumber.of(0); + + TruncatedPacketNumber truncatedPn = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertEquals(packetNumber, truncatedPn.getPacketNumber(largestAckedPn)); + } + + @Test + public void _123456789() throws Exception { + PacketNumber packetNumber = PacketNumber.of(123456789); + PacketNumber largestAckedPn = PacketNumber.of(0); + + TruncatedPacketNumber truncatedPn = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertEquals(packetNumber, truncatedPn.getPacketNumber(largestAckedPn)); + } + + @Test + public void _aabbccdd() throws Exception { + PacketNumber packetNumber = PacketNumber.of(0xaabbccddL); + PacketNumber largestAckedPn = PacketNumber.of(0); + + TruncatedPacketNumber truncatedPn = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertEquals(packetNumber, truncatedPn.getPacketNumber(largestAckedPn)); + } + + @Test + public void _aabbccd0() throws Exception { + PacketNumber packetNumber = PacketNumber.of(0xaabbccddL); + PacketNumber largestAckedPn = PacketNumber.of(0xaabbccd0L); + + TruncatedPacketNumber truncatedPn = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertEquals(packetNumber, truncatedPn.getPacketNumber(largestAckedPn)); + } + + @Test + public void rfc1() throws Exception { + /* + * if an endpoint has received an acknowledgment for packet 0xabe8b3 and is sending a packet with a number of 0xac5c02, + * there are 29,519 (0x734f) outstanding packet numbers. In order to represent at least twice this range (59,038 packets, or 0xe69e), 16 bits are required. + */ + PacketNumber packetNumber = PacketNumber.of(0xac5c02); + PacketNumber largestAckedPn = PacketNumber.of(0xabe8b3); + byte[] truncatedPnBytes = Hex.decodeHex("5c02".toCharArray()); + + TruncatedPacketNumber truncatedPn = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertArrayEquals(truncatedPnBytes, truncatedPn.getBytes()); + } + + @Test + public void rfc2() throws Exception { + /* + * if the highest successfully authenticated packet had a packet number of 0xa82f30ea, * then a packet containing a 16-bit value of 0x9b32 will be decoded as 0xa82f9b32 + */ + byte[] truncatedPnBytes = Hex.decodeHex("9b32".toCharArray()); + PacketNumber packetNumber = PacketNumber.of(0xa82f9b32L); + PacketNumber largestAckedPn = PacketNumber.of(0xa82f30eaL); + + TruncatedPacketNumber truncatedPn1 = new TruncatedPacketNumber(truncatedPnBytes); + TruncatedPacketNumber truncatedPn2 = new TruncatedPacketNumber(packetNumber, largestAckedPn); + assertEquals(packetNumber, truncatedPn1.getPacketNumber(largestAckedPn)); + assertEquals(packetNumber, truncatedPn2.getPacketNumber(largestAckedPn)); + assertEquals(truncatedPn1, truncatedPn2); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/VariableLengthIntegerTest.java b/src/test/java/packetproxy/quic/value/VariableLengthIntegerTest.java new file mode 100644 index 0000000..f89beda --- /dev/null +++ b/src/test/java/packetproxy/quic/value/VariableLengthIntegerTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* https://tools.ietf.org/html/draft-ietf-quic-transport-19#section-16 */ +class VariableLengthIntegerTest { + + @Test + void smoke() throws Exception { + VariableLengthInteger t = VariableLengthInteger.parse(Hex.decodeHex("800061a8".toCharArray())); + assertEquals(25000, t.getValue()); + } + + @Test + void parseSingleByte() throws Exception { + // "and the single byte 25 decodes to 37" + byte[] testBytes = Hex.decodeHex("25".toCharArray()); + VariableLengthInteger var = VariableLengthInteger.parse(ByteBuffer.wrap(testBytes)); + + assertEquals(37, var.getValue()); + assertArrayEquals(testBytes, var.getBytes()); + } + + @Test + void parseTwoBytes() throws Exception { + // "the two byte sequence 7b bd decodes to 15293; " + byte[] testBytes = Hex.decodeHex("7bbd".toCharArray()); + VariableLengthInteger var = VariableLengthInteger.parse(ByteBuffer.wrap(testBytes)); + + assertEquals(15293, var.getValue()); + assertArrayEquals(testBytes, var.getBytes()); + } + + @Test + void parseTwoBytes2() throws Exception { + // "(as does the two byte sequence 40 25)" + byte[] testBytes = Hex.decodeHex("4025".toCharArray()); + VariableLengthInteger var = VariableLengthInteger.parse(ByteBuffer.wrap(testBytes)); + + assertEquals(37, var.getValue()); + } + + @Test + void parseFourBytes() throws Exception { + // "the four byte sequence 9d 7f 3e 7d decodes to 494878333;" + byte[] testBytes = Hex.decodeHex("9d7f3e7d".toCharArray()); + VariableLengthInteger var = VariableLengthInteger.parse(ByteBuffer.wrap(testBytes)); + + assertEquals(494878333, var.getValue()); + assertArrayEquals(testBytes, var.getBytes()); + } + + @Test + void parseEightBytes() throws Exception { + // "the eight byte sequence c2 19 7c 5e ff 14 e8 8c decodes to 151288809941952652;" + byte[] testBytes = Hex.decodeHex("c2197c5eff14e88c".toCharArray()); + VariableLengthInteger var = VariableLengthInteger.parse(ByteBuffer.wrap(testBytes)); + + assertEquals(151288809941952652L, var.getValue()); + assertArrayEquals(testBytes, var.getBytes()); + } + + @Test + void parseExampleBytes() throws Exception { + System.out.println(VariableLengthInteger.parse(Hex.decodeHex("80200000".toCharArray()))); /* 2MB */ + System.out.println(VariableLengthInteger.parse(Hex.decodeHex("80100000".toCharArray()))); /* 1MB */ + System.out.println(VariableLengthInteger.parse(Hex.decodeHex("4201".toCharArray()))); /* 513 */ + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/frame/AckEcnFrameTest.java b/src/test/java/packetproxy/quic/value/frame/AckEcnFrameTest.java new file mode 100644 index 0000000..ebf3be2 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/frame/AckEcnFrameTest.java @@ -0,0 +1,43 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class AckEcnFrameTest { + + @Test + public void smoke() throws Exception { + byte[] test = Hex.decodeHex("030a0001000105000102".toCharArray()); + AckEcnFrame ackEcnFrame = AckEcnFrame.parse(test); + assertThat(ackEcnFrame.getEtc0Count()).isEqualTo(0); + assertThat(ackEcnFrame.getEtc1Count()).isEqualTo(1); + assertThat(ackEcnFrame.getEcnCeCount()).isEqualTo(2); + } + + @Test + public void equalsが正常に動作すること() throws Exception { + byte[] test = Hex.decodeHex("030a0001000105000102".toCharArray()); + AckEcnFrame ackEcnFrame1 = AckEcnFrame.parse(test); + AckEcnFrame ackEcnFrame2 = AckEcnFrame.parse(test); + assertThat(ackEcnFrame1).isEqualTo(ackEcnFrame2); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/frame/AckFrameTest.java b/src/test/java/packetproxy/quic/value/frame/AckFrameTest.java new file mode 100644 index 0000000..5c396f0 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/frame/AckFrameTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class AckFrameTest { + + @Test + public void シンプルなbyteArrayをparseできること() throws Exception { + byte[] test = Hex.decodeHex("0200030000".toCharArray()); + AckFrame ackFrame = AckFrame.parse(test); + assertEquals(0, ackFrame.getLargestAcknowledged()); + assertEquals(3, ackFrame.getAckDelay()); + assertEquals(0, ackFrame.getAckRangeCount()); + assertEquals(0, ackFrame.getFirstAckRange()); + Assertions.assertEquals(0, ackFrame.getAckRanges().size()); + } + + @Test + public void ackRangeがあるbyteArrayをparseできること() throws Exception { + byte[] test = Hex.decodeHex("020a0001000105".toCharArray()); + AckFrame ackFrame = AckFrame.parse(test); + assertEquals(10, ackFrame.getLargestAcknowledged()); + assertEquals(0, ackFrame.getFirstAckRange()); + assertEquals(1, ackFrame.getAckRangeCount()); + Assertions.assertEquals(1, ackFrame.getAckRanges().get(0).getGap()); + Assertions.assertEquals(5, ackFrame.getAckRanges().get(0).getAckRangeLength()); + } + + @Test + public void parseしてgetBytesすると元に戻ること() throws Exception { + byte[] test = Hex.decodeHex("0200030000".toCharArray()); + AckFrame ackFrame = AckFrame.parse(test); + byte[] test2 = ackFrame.getBytes(); + assertArrayEquals(test, test2); + } + + @Test + public void rangeありのbyteをparseしてgetBytesすると元に戻ること() throws Exception { + byte[] test = Hex.decodeHex("020a0001000105".toCharArray()); + AckFrame ackFrame = AckFrame.parse(test); + byte[] test2 = ackFrame.getBytes(); + assertArrayEquals(test, test2); + } + + @Test + public void ackRangeがあるbyteArray2つが等しくなること() throws Exception { + byte[] test1 = Hex.decodeHex("020a0001000105".toCharArray()); + byte[] test2 = Hex.decodeHex("020a0001000105".toCharArray()); + AckFrame ackFrame1 = AckFrame.parse(test1); + AckFrame ackFrame2 = AckFrame.parse(test2); + assertThat(ackFrame1).isEqualTo(ackFrame2); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/frame/CryptoFrameTest.java b/src/test/java/packetproxy/quic/value/frame/CryptoFrameTest.java new file mode 100644 index 0000000..4c81634 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/frame/CryptoFrameTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +class CryptoFrameTest { + + @Test + public void parse後getBytesで元に戻ること() throws Exception { + byte[] testData = Hex.decodeHex("060040f1010000ed0303ebf8fa56f12939b9584a3896472ec40bb863cfd3e86804fe3a47f06a2b69484c00000413011302010000c000000010000e00000b6578616d706c652e636f6dff01000100000a00080006001d0017001800100007000504616c706e000500050100000000003300260024001d00209370b2c9caa47fbabaf4559fedba753de171fa71f50f1ce15d43e994ec74d748002b0003020304000d0010000e0403050306030203080408050806002d00020101001c00024001003900320408ffffffffffffffff05048000ffff07048000ffff0801100104800075300901100f088394c8f03e51570806048000ffff".toCharArray()); + CryptoFrame cryptoFrame = CryptoFrame.parse(testData); + System.out.println(cryptoFrame); + assertArrayEquals(testData, cryptoFrame.getBytes()); + } + + @Test + public void equalsが正常に動作すること() throws Exception { + byte[] data1 = Hex.decodeHex("060040f1010000ed0303ebf8fa56f12939b9584a3896472ec40bb863cfd3e86804fe3a47f06a2b69484c00000413011302010000c000000010000e00000b6578616d706c652e636f6dff01000100000a00080006001d0017001800100007000504616c706e000500050100000000003300260024001d00209370b2c9caa47fbabaf4559fedba753de171fa71f50f1ce15d43e994ec74d748002b0003020304000d0010000e0403050306030203080408050806002d00020101001c00024001003900320408ffffffffffffffff05048000ffff07048000ffff0801100104800075300901100f088394c8f03e51570806048000ffff".toCharArray()); + byte[] data2 = Hex.decodeHex("060040f1010000ed0303ebf8fa56f12939b9584a3896472ec40bb863cfd3e86804fe3a47f06a2b69484c00000413011302010000c000000010000e00000b6578616d706c652e636f6dff01000100000a00080006001d0017001800100007000504616c706e000500050100000000003300260024001d00209370b2c9caa47fbabaf4559fedba753de171fa71f50f1ce15d43e994ec74d748002b0003020304000d0010000e0403050306030203080408050806002d00020101001c00024001003900320408ffffffffffffffff05048000ffff07048000ffff0801100104800075300901100f088394c8f03e51570806048000ffff".toCharArray()); + CryptoFrame cryptoFrame1 = CryptoFrame.parse(data1); + CryptoFrame cryptoFrame2 = CryptoFrame.parse(data2); + assertThat(cryptoFrame1).isEqualTo(cryptoFrame2); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/frame/FramesTest.java b/src/test/java/packetproxy/quic/value/frame/FramesTest.java new file mode 100644 index 0000000..65f9c5d --- /dev/null +++ b/src/test/java/packetproxy/quic/value/frame/FramesTest.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; +import packetproxy.quic.service.frame.Frames; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FramesTest { + + @Test + public void clientHelloSample() throws Exception { + byte[] testData = Hex.decodeHex("060040f1010000ed0303ebf8fa56f12939b9584a3896472ec40bb863cfd3e86804fe3a47f06a2b69484c00000413011302010000c000000010000e00000b6578616d706c652e636f6dff01000100000a00080006001d0017001800100007000504616c706e000500050100000000003300260024001d00209370b2c9caa47fbabaf4559fedba753de171fa71f50f1ce15d43e994ec74d748002b0003020304000d0010000e0403050306030203080408050806002d00020101001c00024001003900320408ffffffffffffffff05048000ffff07048000ffff0801100104800075300901100f088394c8f03e51570806048000ffff".toCharArray()); + List frames = Frames.parse(testData).getFrames(); + assertEquals(1, frames.size()); + assertTrue(frames.get(0) instanceof CryptoFrame); + } + + @Test + public void serverHelloSample() throws Exception { + byte[] testData = Hex.decodeHex("02000300000600407b020000770303afea1ef3ac12e018d10201ef251dab9ca22fb662faa45b5b151126b9a7550a7400130100004f002b000203040033004500170041047254be2b315b10396eeea8b68d454772bf5817e99cb88f5f4aaec6fd12beca033448965c0fd649cc59e6c90698a4f50d388a5c3722fba5770dfbcdbb5063210d0000000000000000".toCharArray()); + List frames = Frames.parse(testData).getFrames(); + assertEquals(3, frames.size()); + assertTrue(frames.get(0) instanceof AckFrame); + assertTrue(frames.get(1) instanceof CryptoFrame); + assertTrue(frames.get(2) instanceof PaddingFrame); + for (Frame frame : frames) { + System.out.println(frame.toString()); + } + } + + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/frame/NewConnectionIdFrameTest.java b/src/test/java/packetproxy/quic/value/frame/NewConnectionIdFrameTest.java new file mode 100644 index 0000000..fe38293 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/frame/NewConnectionIdFrameTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.frame; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; +import packetproxy.quic.value.ConnectionId; +import packetproxy.quic.value.Token; + +import static org.assertj.core.api.Assertions.assertThat; + +class NewConnectionIdFrameTest { + + @Test + public void smoke() throws Exception { + NewConnectionIdFrame frame = new NewConnectionIdFrame(1, 0, ConnectionId.generateRandom(), Token.of(Hex.decodeHex("11223344556677889900112233445566".toCharArray()))); + byte[] data = frame.getBytes(); + + NewConnectionIdFrame frame2 = NewConnectionIdFrame.parse(data); + System.out.println(frame2); + assertThat(frame).isEqualTo(frame2); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/packet/InitialPacketTest.java b/src/test/java/packetproxy/quic/value/packet/InitialPacketTest.java new file mode 100644 index 0000000..cdefe7c --- /dev/null +++ b/src/test/java/packetproxy/quic/value/packet/InitialPacketTest.java @@ -0,0 +1,148 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.packet; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; +import packetproxy.quic.utils.Constants.Role; +import packetproxy.quic.value.key.level.InitialKey; +import packetproxy.quic.value.packet.longheader.LongHeaderPacket; +import packetproxy.quic.value.packet.longheader.pnspace.InitialPacket; +import packetproxy.quic.value.PacketNumber; + +import java.nio.ByteBuffer; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +class InitialPacketTest { + + @Test + void rfc9001のサンプルをparseできること() throws Exception { + // https://www.rfc-editor.org/rfc/rfc9001.html#name-client-initial + // https://www.rfc-editor.org/rfc/rfc9001.html#name-server-initial + byte[] clientReq = Hex.decodeHex("c000000001088394c8f03e5157080000449e7b9aec34d1b1c98dd7689fb8ec11d242b123dc9bd8bab936b47d92ec356c0bab7df5976d27cd449f63300099f3991c260ec4c60d17b31f8429157bb35a1282a643a8d2262cad67500cadb8e7378c8eb7539ec4d4905fed1bee1fc8aafba17c750e2c7ace01e6005f80fcb7df621230c83711b39343fa028cea7f7fb5ff89eac2308249a02252155e2347b63d58c5457afd84d05dfffdb20392844ae812154682e9cf012f9021a6f0be17ddd0c2084dce25ff9b06cde535d0f920a2db1bf362c23e596d11a4f5a6cf3948838a3aec4e15daf8500a6ef69ec4e3feb6b1d98e610ac8b7ec3faf6ad760b7bad1db4ba3485e8a94dc250ae3fdb41ed15fb6a8e5eba0fc3dd60bc8e30c5c4287e53805db059ae0648db2f64264ed5e39be2e20d82df566da8dd5998ccabdae053060ae6c7b4378e846d29f37ed7b4ea9ec5d82e7961b7f25a9323851f681d582363aa5f89937f5a67258bf63ad6f1a0b1d96dbd4faddfcefc5266ba6611722395c906556be52afe3f565636ad1b17d508b73d8743eeb524be22b3dcbc2c7468d54119c7468449a13d8e3b95811a198f3491de3e7fe942b330407abf82a4ed7c1b311663ac69890f4157015853d91e923037c227a33cdd5ec281ca3f79c44546b9d90ca00f064c99e3dd97911d39fe9c5d0b23a229a234cb36186c4819e8b9c5927726632291d6a418211cc2962e20fe47feb3edf330f2c603a9d48c0fcb5699dbfe5896425c5bac4aee82e57a85aaf4e2513e4f05796b07ba2ee47d80506f8d2c25e50fd14de71e6c418559302f939b0e1abd576f279c4b2e0feb85c1f28ff18f58891ffef132eef2fa09346aee33c28eb130ff28f5b766953334113211996d20011a198e3fc433f9f2541010ae17c1bf202580f6047472fb36857fe843b19f5984009ddc324044e847a4f4a0ab34f719595de37252d6235365e9b84392b061085349d73203a4a13e96f5432ec0fd4a1ee65accdd5e3904df54c1da510b0ff20dcc0c77fcb2c0e0eb605cb0504db87632cf3d8b4dae6e705769d1de354270123cb11450efc60ac47683d7b8d0f811365565fd98c4c8eb936bcab8d069fc33bd801b03adea2e1fbc5aa463d08ca19896d2bf59a071b851e6c239052172f296bfb5e72404790a2181014f3b94a4e97d117b438130368cc39dbb2d198065ae3986547926cd2162f40a29f0c3c8745c0f50fba3852e566d44575c29d39a03f0cda721984b6f440591f355e12d439ff150aab7613499dbd49adabc8676eef023b15b65bfc5ca06948109f23f350db82123535eb8a7433bdabcb909271a6ecbcb58b936a88cd4e8f2e6ff5800175f113253d8fa9ca8885c2f552e657dc603f252e1a8e308f76f0be79e2fb8f5d5fbbe2e30ecadd220723c8c0aea8078cdfcb3868263ff8f0940054da48781893a7e49ad5aff4af300cd804a6b6279ab3ff3afb64491c85194aab760d58a606654f9f4400e8b38591356fbf6425aca26dc85244259ff2b19c41b9f96f3ca9ec1dde434da7d2d392b905ddf3d1f9af93d1af5950bd493f5aa731b4056df31bd267b6b90a079831aaf579be0a39013137aac6d404f518cfd46840647e78bfe706ca4cf5e9c5453e9f7cfd2b8b4c8d169a44e55c88d4a9a7f9474241e221af44860018ab0856972e194cd934".toCharArray()); + byte[] serverRes = Hex.decodeHex("cf000000010008f067a5502a4262b5004075c0d95a482cd0991cd25b0aac406a5816b6394100f37a1c69797554780bb38cc5a99f5ede4cf73c3ec2493a1839b3dbcba3f6ea46c5b7684df3548e7ddeb9c3bf9c73cc3f3bded74b562bfb19fb84022f8ef4cdd93795d77d06edbb7aaf2f58891850abbdca3d20398c276456cbc42158407dd074ee".toCharArray()); + + ByteBuffer clientReqBuffer = ByteBuffer.wrap(clientReq); + InitialKey clientInitialKey = InitialKey.of(Role.CLIENT, LongHeaderPacket.getDestConnId(clientReqBuffer)); + InitialKey serverInitialKey = InitialKey.of(Role.SERVER, LongHeaderPacket.getDestConnId(clientReqBuffer)); + + InitialPacket ipCli = new InitialPacket(ByteBuffer.wrap(clientReq), clientInitialKey, PacketNumber.Infinite); + System.out.println(ipCli); + InitialPacket ipServ = new InitialPacket(ByteBuffer.wrap(serverRes), serverInitialKey, PacketNumber.Infinite); + + assertTrue(ipCli.getPayload().length > 0); + assertTrue(ipServ.getPayload().length > 0); + } + + @Test + void 自分のサンプルをparseできること() throws Exception { + byte[] clientReq = Hex.decodeHex("c200000001086a286db0a597681a08c9cda268b4f00fdc0044b69bebcebc42cd8ce39db8a5c8f5cce833444b125120132f8c500c669ecf3b3eeb896af234b985ca641af4c3bd0de24268ec705d638f318cc5974977e46a0d3ca3c22ca3d6c8f11d3aa065ecd7508d889a2fc8e87a82703c122ddb1e21210d0edbfa9c5436c772ec0d889e124bbb2e7c6e48f662578510867b3b7167bfbcc3559ea0a3956042183a8e19c47e5f0d77f8381e9dc7e885dfc8348613252b0b3e00ca2d8c654b1257a33560ced63be19bc6add1b2a3302d6ec8d1b05ac557000a8e7ec40c7fa0728bae04e5531c7521521037861f6a2520c28186ec2a2b6e63ec59c26ef971bf175e1648c22a31a6d4642cea8df02ed63ed84ff4898887e7c6bdb0d741249dd7c53fde19cdd3fbc1447d372755cabed74543175b6f5e6ddd33d1751c0a6ac0393b609a0f83c8698ec50377d5f99461107bde0550cd9bd67bc210858f4e0359b1448042e5c38e79412a800f169b00188fb0dd670d589506c8fc4c4df399a6335cba1339a82f31bc9c47e2aab8bd3821b629405307e11b8c7236b667c6bfc9e5272fa0a76ac4e8b03b605e6d3517fe4aa6657ca14015067b0e64ccc4a1c828a69eff388cb98ff449cafd227ce6b0c64a8556c5c60b8995f15fb21df4f24b4353d2b649dcc7abe0945b089ffcffe0a50529cc41d282c69bc3a29f0741329ac246af2b92ba14263be08f9ece37eb11f1867fcaea40fbaf7459c70d394fdd6abb62f50bbc071fb0adc93017e1524f721cc0e7450e3b21fa59e683da6f724b800a9daf4e3e36e1c04d552cafd2c3b61f76caf12e05fe63e50b6b4590fdeaffe4690286faf1be39a72fd339f9ca46f38349b9dea26bde9d43132f3a8e51ee9da22fc3b1ac099da7847d0ec6ca61849db5c0d16a42da8a1b1d494a5f532c81fcc19215e695b75f50c038c3ccd14ccf72a7d1ead29b8e72f0754a60b3f34322e7fae942306aae73e7be0ca89a7d43ce55cc6b7bc0b844db448cf08e970c49e406240d5fd73ec851fb488acf1e21a578ea318383896ae754ee05ae62a6868db80f5622d495d433412d04cce249c2efbef2bab7d92544797d21938ca55c62a536f11d82260634ebb981580224f1f09c6037af8a94cc53b327afd7a236d2392f17da3a7c7a91b422d604e760f364c2429da2a660736cd95bd025129745beb69987df9e79ef88d22ace6e25d501461417b52700fb14d3aaee2fc183363ebf902b1916a39973f610cd4fff30907a7730f936cfb33e83bc7dca609ba55aeda2c94e5db4c38776bdb48dbfd76c8435d324a0779d0e1177e4e38a79325ce1f177e4ec0c9876bc3dc86d58d8e2eec567a86b819cbb3147651755ee7c1497cbd794927a9771a880a8665bb1f22d4d5e0be269bc196bb285dd7f80a7f05850c8de77814920df0bd9d8133fb569b6158f4db5cf7b1b7921554e3f6f7cc2c0581500ce168da41dea20ca09c5c8367391d09e138176ca4b326aaf3fb4c9cf8cc5a7258eb6927d5b2e8c54b6834e2542831fc5c509fd5f37192fb70ab3d6e6a22c8b0ee99f9cdf3d744774329bf371d8124d4a6e9b376e073df553ccd7e489a03454f565759e2576a9b5bbaf16c74d5c4e99cdc5089b02621692db7176ff9adcc6f28d19d5a97e87d5e198a12c5af15b0fe6fb01f23de0fcf24583813dd0834f0e08f922ac475c00974b19c97e178d4abe43e9599f8ef17ca0ae9bcb3e97".toCharArray()); + byte[] serverRes = Hex.decodeHex("c50000000108c9cda268b4f00fdc09e8881de84c0e4c46d800409670b031b78ac65b9e0c6e56c108dda99a71096cb967b040f3653ce49ffe97d599d4cb9b09504e37bf4a4d8800e3038eb1ccc25497186aad4ae9834b90c93277f9034ee8999a3fb67f248ae106edd8f0ed554c44aa03190114f9b7abb4dbf118b9a3419b2c9cd3f3d299e62131a9924eebf438908ab5e2adec39b3f8bd668b6afdbc3572b026a38aae833ab8cac8d2359b78f379f24ba4e60000000108c9cda268b4f00fdc09e8881de84c0e4c46d84435ff2013623222e045ed0e9a4ac751d9f519c189649d1abad5d43f252c649006425543e3acb285c89c2e58439ba3848c6168c6bdb1ebe63122f491846e656e2563be617a0be79dadb2e816ec1b94265c2ad51e0604f1553b76a0397c10120d192f800c8880c0650b94b0eb40abc81415d22494ced4680e732238423777377e99d592d6437a4d5e3e399aa0d1b2355f28de24c808d7a1f2352d8a86809fcb9296e868f016d7c1d02ac0ef41717fac1addf94da5ccbd61bd05d375bda283a3edfc056c4475fbf0174611faa45efc0d276837070d592bc146868b390fdf938f81882a423c8c3955fa7f00ccb34320991eaceda3ef8f5b8ccf175f19bb23f1cb8d9688e5d3fd0186bbf5c7ec5586dd4d9f84038649ac758a4cf5a1ef41dae9dc18a4a382190d65f45e013a2f3b82f2f82e7945fc4718cbfdad0186b15ac1b3eece41ca55b850626742d40c8240652afbee10f90e7b4d19be18c9af929223998bb4e385bbc0e4f4f4e797c32c014267ac53e1958b60422c7a65fbdb6480fe56dc45ba1b2a6b041c7fe53d96af15dcfb2e404f479cf5a8f55f636569c41534152bdd38b2f1f0f81da73c37fe3e6325a6f86b972318f86d9ac4571f9544ea0a70f7d81b27e5a10a6bbce70c91b2d291a843417d4437f0c9fc67b4af5e8945a8a4fae6be3497a02ad4b7660d429289c0da116723adc63f6d31c5c4184fca4d049f98c005e695bda6824f0ea964d4c39b2dacb66d3eca3fad8c5180539f7110f19f406f1201e432c9cf4d9be239573a41a27506ae633ed4bf5911a0b2ef202e3b465ed196a2da9322ce7a7a434604c26a2b2b2db42ec7a138f1a35941ee40cae800afb37bf3c89fe210849d0f848e2dcd801e355df908bc6f3c28c788c4ff77095be034e03b6f970354d914586db5987b176088938619d38f088b39bdbe38e28ee6e6a1778be5ca1022456080642bad343b5b8655077b7ae8040a8ed6e7afeab8123b9a8100ad2eb46432563047b7583372157ec4825315c3d62ee1262eb5eae3540acd1adb42d8fa5b1e21ab121cb2ed549beaa768ed957b7432d95ffde82ab6e5a2b8efaefc8cb3ef9b98dd588d02a009c9c77cc0c0e82220b38df7b160949c70d0115e93e6574fe5085ac810a9ebcbdda0fc3df6011c9f94b82f2a206534950114315c6be8d8ac092d82593ecce7b572aa4f355b2693ba529988780eaf5c550222b2af51214cf41910b88d7303cd2aa9c52c34c98eafa0583872990a53e5e2f4c6e03814bace7230ea54ebf663b210e341de59bda7b3be04e66c9f90b0fa66761dcbafb188e71edb498b13196139ccdc710a5cc0e08a91c4eefd781b11d4d2be5e98255ae4a90d5820ecfc99ec641d8290509012ab0c38feec7cde28cad2c59e052a600fff5e3a024220482b282bccd705387593899ee85861649becef7da0ba8e97f9c864aff5b15aa7a1cc3a673a7caa1984877f8c2e69b0bf0ab9ff9a558298f14bf82fadb82a4b24d378f69c8e4e2a4eca67ed23421357eb0000000108c9cda268b4f00fdc09e8881de84c0e4c46d844e66f9559e15f2e70e41192f648960b4ea1594857e036fd7464060ea3b4e50e3bd9502e84a2915653e278da821b5479876abe6d8b40b8583a13adc2c917aff0e7706759e5b7dcd95a939c00ef6bf1717a36083fccccc694a09b5efb8f60198e2d17557bb3c642672acbd102e9b1b3b0a090a018816a9dfa17565d5857514bbd37bb4e9eb1aed951d16ca2632fcdabefea8bac815e0845b4f6c85a44263be70aae7854de99b8b6f661680d05eb9214b7425d1263288089c36394c835b6ecf238b801927bc753b8f1adb60f1136b3cbc9bb57308e7c3359074d512cd1816bfc455d1fc91512c7e7f7b6c73e59dfe1b3dc19edb625e838fdd591a30c86b751c82ce95e7691b1df421fcb1505233d041504e64c842f8c46840a14df0a3ac8be09db4418e9462c6b705ed87135b3b1353e3853a7561ae22a4a34a7f8fc03a2f286067daac9af9e88340c52803b397d5d3d37e433ed1dd40eeaaea094fd90f1b4212d0b92fbd8b741e5503d6564b0ec3610d6425b016c7657d1012b94432fb65ee169a6799025c120e807d2618cba527728400713dd3ac8553b33e84dac126423cbae1c5e997a329a5576bf07ea240878b21d2d89b41a0b7db0771e6f0c8ff49ee3d28fae5d7cf681f4f7c61eb3abebab42f841d39604996e25b2f93ff1383da51529397e3d835df01174058900b428ee748cd11483bbe2e99b29cf505c41956f73f50cacfde39d00db409c3447727bfef9cef784ccaeebb9d1770b9cebd781f91e372087e2f13349c5c5a36d22c8cffc3f244bf061436543ff191453269207b99d0b95f755511ff61406313fdc588dfcb599cd360c508ae83805f486c1483c74d357d3e11e6eb4f2a67a485bebafe590f8b3bf9bac4e8de11058e94328935d7e52bf3e4ff8084bf324491845dad8bf9ff508badd7d53294e1320f75258b10da4e8552f49ac98aaf671988a021b025f217b7efa22ed0b38a838c8530201db8ffa751d9c419e5235748a5d47a1314d902e67419a610a0777386b13f28ce3880fbe94c3f1ded8458a93c54a".toCharArray()); + + ByteBuffer clientReqBuffer = ByteBuffer.wrap(clientReq); + InitialKey clientInitialKey = InitialKey.of(Role.CLIENT, LongHeaderPacket.getDestConnId(clientReqBuffer)); + InitialKey serverInitialKey = InitialKey.of(Role.SERVER, LongHeaderPacket.getDestConnId(clientReqBuffer)); + + InitialPacket ipCli = new InitialPacket(ByteBuffer.wrap(clientReq), clientInitialKey, PacketNumber.Infinite); + InitialPacket ipServ = new InitialPacket(ByteBuffer.wrap(serverRes), serverInitialKey, PacketNumber.Infinite); + + System.out.println(ipCli); + System.out.println(ipServ); + + assertTrue(ipCli.getPayload().length > 0); + assertTrue(ipServ.getPayload().length > 0); + } + + @Test + void 自分のサンプルをparseできること2() throws Exception { + byte[] clientReq = Hex.decodeHex("c100000001081befef1be1a7df1908cbfff1469abaf57d2fd8013f2dc145e5be5073c2cb448fad4afec36a9baac0a2801344f4907f83d5312c9f4bc10a2994d959c93bfc24cb8f4209168cda7c355f9411e1ecfa1a28d705a52d6a4089b4336a0cf2d574442956a476c0246d83dbf5b26810057134c69953b362197fcd79bf5b4e4687e4845bb50a852b68b97c8e52bb317b4f3e2ccdd3a0a61a9bdb7f2f474b6e380af33965a49ae3d1f7189320481506b39ab118ef59da6f51d95f1822284a239c717cff88ca8875f6ebb7293968eae392bfc10edb7768f229cf8eaa16b89edd10774d53623b4600249409d918cc4d580c8a71acbca1ebf2f4d2c2f5202f1382e35d19476a17c563d3e00fbfc1e94696224d76acfc3a514a9156a0030a8e0058af2e209784692f919db1032845b73ff068ce0f8ff6643e9630123fa0a9481d12ec2cf99c56c32ddd8aa44c59802bba442f693dfcbbcbabff3912ec7ee6827685e04db63a674f3fbe160cf625e9ddd432a285a1f533e5285978974ba0578fa2fdc5331feaa1b6ffb21fb57fa9b87ad8311e6c903620f7d4d157a9fdafd8f460309be817cb36a594a5992296a37a7e13b5a292644ddc64dd9884db89961e9d2b733b6de0ac4529fd4d513259ab4480e64fe7850bdd28864438c4b62650bb34c6df4b918b56a524541332856dc6d679df9a0bdb35eb211a82b8b40e77d0a6656d44121dc2ec8cae91bd633ce35c93759e92db89da1ec14dba5b24d3b34d6eef28354f1ccaf3313a8d82457bc99ea840b65dbeea0616463062cdd1931a0c21ef978f12ec87f5ad0ccedc0f11e507516fa35b53d500000001081befef1be1a7df1908cbfff1469abaf57d4279a671beccd6fdeb33f4fc0e094c3897e322549ab79f7c112c8e829f66abf424a7ebef5775bfcd26b65108e1d0e87eeb564bf0bf80ed0444ce6a3cb81a2c0735ddb5db902f6c9b3d27eb10b7c4fd6bb3cc1b5bbba9f26e154a5c6787aedf93b75b1b89ac436875dca3d935bec76d12ab17e684fe631d55d118ec4f9f21f34aa267593079ab0fe5609596e840af8a90cc6af6b3706838408669432ea150d76f149e490ecfe8deb17301cbe7563048e4f09d72f8a0937229a29128ad51bac7f806e0a7f149c189ad44326128daba5fcdc3ad2c9304d2656bc541642501077937a2cd7245eb7252428af94c5102efe2ebb2143b777d8042f1b8c3ff4e002bcc0f30f4ba898ef9f503a963a0096620dc12713fa3b0ba0ef791db14bf8b1c5912a6ca7a42c511036c932186cef3009ba9006eba6588d003bf2ae5feac7055832e247704cf96ed631e8cfa2cc1b17c76e40709bacb9f554140ab2e966974eb4fa83c87b50b9e7708cc47d42fcb41a44adcacc3e36ea163c05cec39478f97cd4a4dd867a8fcf3e389c095fe681250d863bb5c3596a60b61e6661233a7dd5d05b85a4f106b6c87dac91d6cff739917d097c0256d4ee1acc6ee3bd3f071c325f92f2efe0f9798ed03029b5fffe6f26bc0ea9b443e147f8b6a9cb79c5ef1f5f0d4c4c15ee425c8fa74668f529db6e28cf50ddd549e745462d422c56e24bfc40ce368b693b137735d0a331bfbeb94fc1390417d6e406cc23ae40c1a34aa6872e6e5b249cf295674b63e0b598dd4b06ae2483229a158bc20e35f859c8c5a292e5bed379d05b296027976345ded4e1081cc8b29c1942be947e1eacbb16c414d65b4f76fd43b1257ba72533ea491b4f87a708ef4be49714655fb468a4fc247aee8".toCharArray()); + + ByteBuffer clientReqBuffer = ByteBuffer.wrap(clientReq); + InitialKey clientInitialKey = InitialKey.of(Role.CLIENT, LongHeaderPacket.getDestConnId(clientReqBuffer)); + + InitialPacket ipCli = new InitialPacket(ByteBuffer.wrap(clientReq), clientInitialKey, PacketNumber.Infinite); + System.out.println(ipCli); + assertTrue(ipCli.getPayload().length > 0); + } + + @Test + void rfc9001のサンプルをparse後getBytesできること() throws Exception { + // https://www.rfc-editor.org/rfc/rfc9001.html#name-client-initial + byte[] clientReq = Hex.decodeHex("c000000001088394c8f03e5157080000449e7b9aec34d1b1c98dd7689fb8ec11d242b123dc9bd8bab936b47d92ec356c0bab7df5976d27cd449f63300099f3991c260ec4c60d17b31f8429157bb35a1282a643a8d2262cad67500cadb8e7378c8eb7539ec4d4905fed1bee1fc8aafba17c750e2c7ace01e6005f80fcb7df621230c83711b39343fa028cea7f7fb5ff89eac2308249a02252155e2347b63d58c5457afd84d05dfffdb20392844ae812154682e9cf012f9021a6f0be17ddd0c2084dce25ff9b06cde535d0f920a2db1bf362c23e596d11a4f5a6cf3948838a3aec4e15daf8500a6ef69ec4e3feb6b1d98e610ac8b7ec3faf6ad760b7bad1db4ba3485e8a94dc250ae3fdb41ed15fb6a8e5eba0fc3dd60bc8e30c5c4287e53805db059ae0648db2f64264ed5e39be2e20d82df566da8dd5998ccabdae053060ae6c7b4378e846d29f37ed7b4ea9ec5d82e7961b7f25a9323851f681d582363aa5f89937f5a67258bf63ad6f1a0b1d96dbd4faddfcefc5266ba6611722395c906556be52afe3f565636ad1b17d508b73d8743eeb524be22b3dcbc2c7468d54119c7468449a13d8e3b95811a198f3491de3e7fe942b330407abf82a4ed7c1b311663ac69890f4157015853d91e923037c227a33cdd5ec281ca3f79c44546b9d90ca00f064c99e3dd97911d39fe9c5d0b23a229a234cb36186c4819e8b9c5927726632291d6a418211cc2962e20fe47feb3edf330f2c603a9d48c0fcb5699dbfe5896425c5bac4aee82e57a85aaf4e2513e4f05796b07ba2ee47d80506f8d2c25e50fd14de71e6c418559302f939b0e1abd576f279c4b2e0feb85c1f28ff18f58891ffef132eef2fa09346aee33c28eb130ff28f5b766953334113211996d20011a198e3fc433f9f2541010ae17c1bf202580f6047472fb36857fe843b19f5984009ddc324044e847a4f4a0ab34f719595de37252d6235365e9b84392b061085349d73203a4a13e96f5432ec0fd4a1ee65accdd5e3904df54c1da510b0ff20dcc0c77fcb2c0e0eb605cb0504db87632cf3d8b4dae6e705769d1de354270123cb11450efc60ac47683d7b8d0f811365565fd98c4c8eb936bcab8d069fc33bd801b03adea2e1fbc5aa463d08ca19896d2bf59a071b851e6c239052172f296bfb5e72404790a2181014f3b94a4e97d117b438130368cc39dbb2d198065ae3986547926cd2162f40a29f0c3c8745c0f50fba3852e566d44575c29d39a03f0cda721984b6f440591f355e12d439ff150aab7613499dbd49adabc8676eef023b15b65bfc5ca06948109f23f350db82123535eb8a7433bdabcb909271a6ecbcb58b936a88cd4e8f2e6ff5800175f113253d8fa9ca8885c2f552e657dc603f252e1a8e308f76f0be79e2fb8f5d5fbbe2e30ecadd220723c8c0aea8078cdfcb3868263ff8f0940054da48781893a7e49ad5aff4af300cd804a6b6279ab3ff3afb64491c85194aab760d58a606654f9f4400e8b38591356fbf6425aca26dc85244259ff2b19c41b9f96f3ca9ec1dde434da7d2d392b905ddf3d1f9af93d1af5950bd493f5aa731b4056df31bd267b6b90a079831aaf579be0a39013137aac6d404f518cfd46840647e78bfe706ca4cf5e9c5453e9f7cfd2b8b4c8d169a44e55c88d4a9a7f9474241e221af44860018ab0856972e194cd934".toCharArray()); + byte[] serverRes = Hex.decodeHex("cf000000010008f067a5502a4262b5004075c0d95a482cd0991cd25b0aac406a5816b6394100f37a1c69797554780bb38cc5a99f5ede4cf73c3ec2493a1839b3dbcba3f6ea46c5b7684df3548e7ddeb9c3bf9c73cc3f3bded74b562bfb19fb84022f8ef4cdd93795d77d06edbb7aaf2f58891850abbdca3d20398c276456cbc42158407dd074ee".toCharArray()); + + ByteBuffer clientReqBuffer = ByteBuffer.wrap(clientReq); + InitialKey clientInitialKey = InitialKey.of(Role.CLIENT, LongHeaderPacket.getDestConnId(clientReqBuffer)); + InitialKey serverInitialKey = InitialKey.of(Role.SERVER, LongHeaderPacket.getDestConnId(clientReqBuffer)); + + InitialPacket ipCli = new InitialPacket(ByteBuffer.wrap(clientReq), clientInitialKey, PacketNumber.Infinite); + InitialPacket ipServ = new InitialPacket(ByteBuffer.wrap(serverRes), serverInitialKey, PacketNumber.Infinite); + + byte[] ipCliBytes = ipCli.getBytes(clientInitialKey, PacketNumber.Infinite); + byte[] ipServBytes = ipServ.getBytes(serverInitialKey, PacketNumber.Infinite); + assertTrue(ipCliBytes.length > 0); + assertTrue(serverRes.length > 0); + } + + @Test + void equalsが正常に動作すること() throws Exception { + // https://www.rfc-editor.org/rfc/rfc9001.html#name-client-initial + byte[] clientReq = Hex.decodeHex("c000000001088394c8f03e5157080000449e7b9aec34d1b1c98dd7689fb8ec11d242b123dc9bd8bab936b47d92ec356c0bab7df5976d27cd449f63300099f3991c260ec4c60d17b31f8429157bb35a1282a643a8d2262cad67500cadb8e7378c8eb7539ec4d4905fed1bee1fc8aafba17c750e2c7ace01e6005f80fcb7df621230c83711b39343fa028cea7f7fb5ff89eac2308249a02252155e2347b63d58c5457afd84d05dfffdb20392844ae812154682e9cf012f9021a6f0be17ddd0c2084dce25ff9b06cde535d0f920a2db1bf362c23e596d11a4f5a6cf3948838a3aec4e15daf8500a6ef69ec4e3feb6b1d98e610ac8b7ec3faf6ad760b7bad1db4ba3485e8a94dc250ae3fdb41ed15fb6a8e5eba0fc3dd60bc8e30c5c4287e53805db059ae0648db2f64264ed5e39be2e20d82df566da8dd5998ccabdae053060ae6c7b4378e846d29f37ed7b4ea9ec5d82e7961b7f25a9323851f681d582363aa5f89937f5a67258bf63ad6f1a0b1d96dbd4faddfcefc5266ba6611722395c906556be52afe3f565636ad1b17d508b73d8743eeb524be22b3dcbc2c7468d54119c7468449a13d8e3b95811a198f3491de3e7fe942b330407abf82a4ed7c1b311663ac69890f4157015853d91e923037c227a33cdd5ec281ca3f79c44546b9d90ca00f064c99e3dd97911d39fe9c5d0b23a229a234cb36186c4819e8b9c5927726632291d6a418211cc2962e20fe47feb3edf330f2c603a9d48c0fcb5699dbfe5896425c5bac4aee82e57a85aaf4e2513e4f05796b07ba2ee47d80506f8d2c25e50fd14de71e6c418559302f939b0e1abd576f279c4b2e0feb85c1f28ff18f58891ffef132eef2fa09346aee33c28eb130ff28f5b766953334113211996d20011a198e3fc433f9f2541010ae17c1bf202580f6047472fb36857fe843b19f5984009ddc324044e847a4f4a0ab34f719595de37252d6235365e9b84392b061085349d73203a4a13e96f5432ec0fd4a1ee65accdd5e3904df54c1da510b0ff20dcc0c77fcb2c0e0eb605cb0504db87632cf3d8b4dae6e705769d1de354270123cb11450efc60ac47683d7b8d0f811365565fd98c4c8eb936bcab8d069fc33bd801b03adea2e1fbc5aa463d08ca19896d2bf59a071b851e6c239052172f296bfb5e72404790a2181014f3b94a4e97d117b438130368cc39dbb2d198065ae3986547926cd2162f40a29f0c3c8745c0f50fba3852e566d44575c29d39a03f0cda721984b6f440591f355e12d439ff150aab7613499dbd49adabc8676eef023b15b65bfc5ca06948109f23f350db82123535eb8a7433bdabcb909271a6ecbcb58b936a88cd4e8f2e6ff5800175f113253d8fa9ca8885c2f552e657dc603f252e1a8e308f76f0be79e2fb8f5d5fbbe2e30ecadd220723c8c0aea8078cdfcb3868263ff8f0940054da48781893a7e49ad5aff4af300cd804a6b6279ab3ff3afb64491c85194aab760d58a606654f9f4400e8b38591356fbf6425aca26dc85244259ff2b19c41b9f96f3ca9ec1dde434da7d2d392b905ddf3d1f9af93d1af5950bd493f5aa731b4056df31bd267b6b90a079831aaf579be0a39013137aac6d404f518cfd46840647e78bfe706ca4cf5e9c5453e9f7cfd2b8b4c8d169a44e55c88d4a9a7f9474241e221af44860018ab0856972e194cd934".toCharArray()); + + ByteBuffer clientReqBuffer = ByteBuffer.wrap(clientReq); + InitialKey clientInitialKey = InitialKey.of(Role.CLIENT, LongHeaderPacket.getDestConnId(clientReqBuffer)); + + InitialPacket ipCli1 = new InitialPacket(ByteBuffer.wrap(clientReq), clientInitialKey, PacketNumber.Infinite); + InitialPacket ipCli2 = new InitialPacket(ByteBuffer.wrap(clientReq), clientInitialKey, PacketNumber.Infinite); + + assertThat(ipCli1).isEqualTo(ipCli2); + } + + @Test + void equalsが正常に動作すること2() throws Exception { + byte[] req1 = Hex.decodeHex("c000000001088394c8f03e5157080000449e7b9aec34d1b1c98dd7689fb8ec11d242b123dc9bd8bab936b47d92ec356c0bab7df5976d27cd449f63300099f3991c260ec4c60d17b31f8429157bb35a1282a643a8d2262cad67500cadb8e7378c8eb7539ec4d4905fed1bee1fc8aafba17c750e2c7ace01e6005f80fcb7df621230c83711b39343fa028cea7f7fb5ff89eac2308249a02252155e2347b63d58c5457afd84d05dfffdb20392844ae812154682e9cf012f9021a6f0be17ddd0c2084dce25ff9b06cde535d0f920a2db1bf362c23e596d11a4f5a6cf3948838a3aec4e15daf8500a6ef69ec4e3feb6b1d98e610ac8b7ec3faf6ad760b7bad1db4ba3485e8a94dc250ae3fdb41ed15fb6a8e5eba0fc3dd60bc8e30c5c4287e53805db059ae0648db2f64264ed5e39be2e20d82df566da8dd5998ccabdae053060ae6c7b4378e846d29f37ed7b4ea9ec5d82e7961b7f25a9323851f681d582363aa5f89937f5a67258bf63ad6f1a0b1d96dbd4faddfcefc5266ba6611722395c906556be52afe3f565636ad1b17d508b73d8743eeb524be22b3dcbc2c7468d54119c7468449a13d8e3b95811a198f3491de3e7fe942b330407abf82a4ed7c1b311663ac69890f4157015853d91e923037c227a33cdd5ec281ca3f79c44546b9d90ca00f064c99e3dd97911d39fe9c5d0b23a229a234cb36186c4819e8b9c5927726632291d6a418211cc2962e20fe47feb3edf330f2c603a9d48c0fcb5699dbfe5896425c5bac4aee82e57a85aaf4e2513e4f05796b07ba2ee47d80506f8d2c25e50fd14de71e6c418559302f939b0e1abd576f279c4b2e0feb85c1f28ff18f58891ffef132eef2fa09346aee33c28eb130ff28f5b766953334113211996d20011a198e3fc433f9f2541010ae17c1bf202580f6047472fb36857fe843b19f5984009ddc324044e847a4f4a0ab34f719595de37252d6235365e9b84392b061085349d73203a4a13e96f5432ec0fd4a1ee65accdd5e3904df54c1da510b0ff20dcc0c77fcb2c0e0eb605cb0504db87632cf3d8b4dae6e705769d1de354270123cb11450efc60ac47683d7b8d0f811365565fd98c4c8eb936bcab8d069fc33bd801b03adea2e1fbc5aa463d08ca19896d2bf59a071b851e6c239052172f296bfb5e72404790a2181014f3b94a4e97d117b438130368cc39dbb2d198065ae3986547926cd2162f40a29f0c3c8745c0f50fba3852e566d44575c29d39a03f0cda721984b6f440591f355e12d439ff150aab7613499dbd49adabc8676eef023b15b65bfc5ca06948109f23f350db82123535eb8a7433bdabcb909271a6ecbcb58b936a88cd4e8f2e6ff5800175f113253d8fa9ca8885c2f552e657dc603f252e1a8e308f76f0be79e2fb8f5d5fbbe2e30ecadd220723c8c0aea8078cdfcb3868263ff8f0940054da48781893a7e49ad5aff4af300cd804a6b6279ab3ff3afb64491c85194aab760d58a606654f9f4400e8b38591356fbf6425aca26dc85244259ff2b19c41b9f96f3ca9ec1dde434da7d2d392b905ddf3d1f9af93d1af5950bd493f5aa731b4056df31bd267b6b90a079831aaf579be0a39013137aac6d404f518cfd46840647e78bfe706ca4cf5e9c5453e9f7cfd2b8b4c8d169a44e55c88d4a9a7f9474241e221af44860018ab0856972e194cd934".toCharArray()); + byte[] req2 = Hex.decodeHex("c000000001088394c8f03e5157080000449e7b9aec34d1b1c98dd7689fb8ec11d242b123dc9bd8bab936b47d92ec356c0bab7df5976d27cd449f63300099f3991c260ec4c60d17b31f8429157bb35a1282a643a8d2262cad67500cadb8e7378c8eb7539ec4d4905fed1bee1fc8aafba17c750e2c7ace01e6005f80fcb7df621230c83711b39343fa028cea7f7fb5ff89eac2308249a02252155e2347b63d58c5457afd84d05dfffdb20392844ae812154682e9cf012f9021a6f0be17ddd0c2084dce25ff9b06cde535d0f920a2db1bf362c23e596d11a4f5a6cf3948838a3aec4e15daf8500a6ef69ec4e3feb6b1d98e610ac8b7ec3faf6ad760b7bad1db4ba3485e8a94dc250ae3fdb41ed15fb6a8e5eba0fc3dd60bc8e30c5c4287e53805db059ae0648db2f64264ed5e39be2e20d82df566da8dd5998ccabdae053060ae6c7b4378e846d29f37ed7b4ea9ec5d82e7961b7f25a9323851f681d582363aa5f89937f5a67258bf63ad6f1a0b1d96dbd4faddfcefc5266ba6611722395c906556be52afe3f565636ad1b17d508b73d8743eeb524be22b3dcbc2c7468d54119c7468449a13d8e3b95811a198f3491de3e7fe942b330407abf82a4ed7c1b311663ac69890f4157015853d91e923037c227a33cdd5ec281ca3f79c44546b9d90ca00f064c99e3dd97911d39fe9c5d0b23a229a234cb36186c4819e8b9c5927726632291d6a418211cc2962e20fe47feb3edf330f2c603a9d48c0fcb5699dbfe5896425c5bac4aee82e57a85aaf4e2513e4f05796b07ba2ee47d80506f8d2c25e50fd14de71e6c418559302f939b0e1abd576f279c4b2e0feb85c1f28ff18f58891ffef132eef2fa09346aee33c28eb130ff28f5b766953334113211996d20011a198e3fc433f9f2541010ae17c1bf202580f6047472fb36857fe843b19f5984009ddc324044e847a4f4a0ab34f719595de37252d6235365e9b84392b061085349d73203a4a13e96f5432ec0fd4a1ee65accdd5e3904df54c1da510b0ff20dcc0c77fcb2c0e0eb605cb0504db87632cf3d8b4dae6e705769d1de354270123cb11450efc60ac47683d7b8d0f811365565fd98c4c8eb936bcab8d069fc33bd801b03adea2e1fbc5aa463d08ca19896d2bf59a071b851e6c239052172f296bfb5e72404790a2181014f3b94a4e97d117b438130368cc39dbb2d198065ae3986547926cd2162f40a29f0c3c8745c0f50fba3852e566d44575c29d39a03f0cda721984b6f440591f355e12d439ff150aab7613499dbd49adabc8676eef023b15b65bfc5ca06948109f23f350db82123535eb8a7433bdabcb909271a6ecbcb58b936a88cd4e8f2e6ff5800175f113253d8fa9ca8885c2f552e657dc603f252e1a8e308f76f0be79e2fb8f5d5fbbe2e30ecadd220723c8c0aea8078cdfcb3868263ff8f0940054da48781893a7e49ad5aff4af300cd804a6b6279ab3ff3afb64491c85194aab760d58a606654f9f4400e8b38591356fbf6425aca26dc85244259ff2b19c41b9f96f3ca9ec1dde434da7d2d392b905ddf3d1f9af93d1af5950bd493f5aa731b4056df31bd267b6b90a079831aaf579be0a39013137aac6d404f518cfd46840647e78bfe706ca4cf5e9c5453e9f7cfd2b8b4c8d169a44e55c88d4a9a7f9474241e221af44860018ab0856972e194cd934".toCharArray()); + + ByteBuffer clientReqBuffer = ByteBuffer.wrap(req1); + InitialKey clientInitialKey = InitialKey.of(Role.CLIENT, LongHeaderPacket.getDestConnId(clientReqBuffer)); + + InitialPacket ipCli1 = new InitialPacket(ByteBuffer.wrap(req1), clientInitialKey, PacketNumber.Infinite); + InitialPacket ipCli2 = new InitialPacket(ByteBuffer.wrap(req2), clientInitialKey, PacketNumber.Infinite); + assertThat(ipCli1).isEqualTo(ipCli2); + } + + @Test + void parseとgetBytesを複数回繰り返せること() throws Exception { + byte[] req1 = Hex.decodeHex("c000000001088394c8f03e5157080000449e7b9aec34d1b1c98dd7689fb8ec11d242b123dc9bd8bab936b47d92ec356c0bab7df5976d27cd449f63300099f3991c260ec4c60d17b31f8429157bb35a1282a643a8d2262cad67500cadb8e7378c8eb7539ec4d4905fed1bee1fc8aafba17c750e2c7ace01e6005f80fcb7df621230c83711b39343fa028cea7f7fb5ff89eac2308249a02252155e2347b63d58c5457afd84d05dfffdb20392844ae812154682e9cf012f9021a6f0be17ddd0c2084dce25ff9b06cde535d0f920a2db1bf362c23e596d11a4f5a6cf3948838a3aec4e15daf8500a6ef69ec4e3feb6b1d98e610ac8b7ec3faf6ad760b7bad1db4ba3485e8a94dc250ae3fdb41ed15fb6a8e5eba0fc3dd60bc8e30c5c4287e53805db059ae0648db2f64264ed5e39be2e20d82df566da8dd5998ccabdae053060ae6c7b4378e846d29f37ed7b4ea9ec5d82e7961b7f25a9323851f681d582363aa5f89937f5a67258bf63ad6f1a0b1d96dbd4faddfcefc5266ba6611722395c906556be52afe3f565636ad1b17d508b73d8743eeb524be22b3dcbc2c7468d54119c7468449a13d8e3b95811a198f3491de3e7fe942b330407abf82a4ed7c1b311663ac69890f4157015853d91e923037c227a33cdd5ec281ca3f79c44546b9d90ca00f064c99e3dd97911d39fe9c5d0b23a229a234cb36186c4819e8b9c5927726632291d6a418211cc2962e20fe47feb3edf330f2c603a9d48c0fcb5699dbfe5896425c5bac4aee82e57a85aaf4e2513e4f05796b07ba2ee47d80506f8d2c25e50fd14de71e6c418559302f939b0e1abd576f279c4b2e0feb85c1f28ff18f58891ffef132eef2fa09346aee33c28eb130ff28f5b766953334113211996d20011a198e3fc433f9f2541010ae17c1bf202580f6047472fb36857fe843b19f5984009ddc324044e847a4f4a0ab34f719595de37252d6235365e9b84392b061085349d73203a4a13e96f5432ec0fd4a1ee65accdd5e3904df54c1da510b0ff20dcc0c77fcb2c0e0eb605cb0504db87632cf3d8b4dae6e705769d1de354270123cb11450efc60ac47683d7b8d0f811365565fd98c4c8eb936bcab8d069fc33bd801b03adea2e1fbc5aa463d08ca19896d2bf59a071b851e6c239052172f296bfb5e72404790a2181014f3b94a4e97d117b438130368cc39dbb2d198065ae3986547926cd2162f40a29f0c3c8745c0f50fba3852e566d44575c29d39a03f0cda721984b6f440591f355e12d439ff150aab7613499dbd49adabc8676eef023b15b65bfc5ca06948109f23f350db82123535eb8a7433bdabcb909271a6ecbcb58b936a88cd4e8f2e6ff5800175f113253d8fa9ca8885c2f552e657dc603f252e1a8e308f76f0be79e2fb8f5d5fbbe2e30ecadd220723c8c0aea8078cdfcb3868263ff8f0940054da48781893a7e49ad5aff4af300cd804a6b6279ab3ff3afb64491c85194aab760d58a606654f9f4400e8b38591356fbf6425aca26dc85244259ff2b19c41b9f96f3ca9ec1dde434da7d2d392b905ddf3d1f9af93d1af5950bd493f5aa731b4056df31bd267b6b90a079831aaf579be0a39013137aac6d404f518cfd46840647e78bfe706ca4cf5e9c5453e9f7cfd2b8b4c8d169a44e55c88d4a9a7f9474241e221af44860018ab0856972e194cd934".toCharArray()); + + ByteBuffer clientReqBuffer = ByteBuffer.wrap(req1); + InitialKey clientInitialKey = InitialKey.of(Role.CLIENT, LongHeaderPacket.getDestConnId(clientReqBuffer)); + + InitialPacket ipCli1 = new InitialPacket(ByteBuffer.wrap(req1), clientInitialKey, PacketNumber.Infinite); + byte[] req2 = ipCli1.getBytes(clientInitialKey, PacketNumber.Infinite); + InitialPacket ipCli2 = new InitialPacket(ByteBuffer.wrap(req2), clientInitialKey, PacketNumber.Infinite); + byte[] req3 = ipCli2.getBytes(clientInitialKey, PacketNumber.Infinite); + InitialPacket ipCli3 = new InitialPacket(ByteBuffer.wrap(req3), clientInitialKey, PacketNumber.Infinite); + byte[] req4 = ipCli3.getBytes(clientInitialKey, PacketNumber.Infinite); + assertThat(ipCli2).isEqualTo(ipCli3); + } + + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/packet/helper/TestPacket.java b/src/test/java/packetproxy/quic/value/packet/helper/TestPacket.java new file mode 100644 index 0000000..7b26776 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/packet/helper/TestPacket.java @@ -0,0 +1,82 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.packet.helper; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import lombok.Value; +import packetproxy.quic.utils.Constants.PnSpaceType; +import packetproxy.quic.value.frame.AckFrame; +import packetproxy.quic.service.frame.Frames; +import packetproxy.quic.service.frame.FramesBuilder; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.value.packet.PnSpacePacket; +import packetproxy.quic.value.packet.QuicPacket; + +import java.util.Optional; + +import static packetproxy.quic.utils.Constants.PnSpaceType.PnSpaceInitial; + +@ToString(callSuper = true) +@EqualsAndHashCode(callSuper = true) +@Value +public class TestPacket extends QuicPacket implements PnSpacePacket { + + static public final byte TYPE = 0x0; + + static public boolean is(byte type) { + return (type & (byte)0xf0) == TYPE; + } + + static public TestPacket of(PacketNumber packetNumber, AckFrame ackFrame) { + return new TestPacket(TYPE, packetNumber, new FramesBuilder().add(ackFrame).build()); + } + + static public TestPacket of(PacketNumber packetNumber) { + return new TestPacket(TYPE, packetNumber, Frames.empty); + } + + PacketNumber packetNumber; + Frames frames; + + public TestPacket(byte type, PacketNumber packetNumber, Frames frames) { + super(type); + this.packetNumber = packetNumber; + this.frames = frames; + } + + @Override + public boolean isAckEliciting() { + return frames.isAckEliciting(); + } + + @Override + public boolean hasAckFrame() { + return frames.hasAckFrame(); + } + + @Override + public Optional getAckFrame() { + return frames.getAckFrame(); + } + + @Override + public PnSpaceType getPnSpaceType() { + return PnSpaceInitial; + } + +} diff --git a/src/test/java/packetproxy/quic/value/packet/shortheader/ShortHeaderPacketTest.java b/src/test/java/packetproxy/quic/value/packet/shortheader/ShortHeaderPacketTest.java new file mode 100644 index 0000000..0539528 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/packet/shortheader/ShortHeaderPacketTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2022 DeNA Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package packetproxy.quic.value.packet.shortheader; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; +import packetproxy.quic.value.PacketNumber; +import packetproxy.quic.value.key.Key; + +import java.nio.ByteBuffer; + +import static org.assertj.core.api.Assertions.assertThat; + +class ShortHeaderPacketTest { + + @Test + public void dataをインスタンス化した後getBytesで元に戻ること() throws Exception { + byte[] data = Hex.decodeHex("5d7be5e8ca341134a2a0de5cced82ebb3f369cc7035c0b52465da032887b7a0c".toCharArray()); + byte[] secret = Hex.decodeHex("24ba37689b6e1e5ed9e1fbbf563718baeb2f11e4d1da18a04218761b386ab269".toCharArray()); + Key key = Key.of(secret); + ShortHeaderPacket packet = new ShortHeaderPacket(ByteBuffer.wrap(data), key, PacketNumber.Infinite); + byte[] restoredData = packet.getBytes(key, PacketNumber.Infinite); + assertThat(data).isEqualTo(restoredData); + } + +} \ No newline at end of file diff --git a/src/test/java/packetproxy/quic/value/transportparameter/bool/DisableActiveMigrationParameterTest.java b/src/test/java/packetproxy/quic/value/transportparameter/bool/DisableActiveMigrationParameterTest.java new file mode 100644 index 0000000..87cbfe3 --- /dev/null +++ b/src/test/java/packetproxy/quic/value/transportparameter/bool/DisableActiveMigrationParameterTest.java @@ -0,0 +1,14 @@ +package packetproxy.quic.value.transportparameter.bool; + +import org.apache.commons.codec.binary.Hex; +import org.junit.jupiter.api.Test; + +class DisableActiveMigrationParameterTest { + + @Test + public void smoke() { + DisableActiveMigrationParameter param = new DisableActiveMigrationParameter(); + System.out.println(Hex.encodeHexString(param.getBytes())); + } + +} \ No newline at end of file