├── .gitignore ├── CONTRIBUTING ├── CONTRIBUTORS ├── LICENSE ├── Makefile ├── README.md ├── example ├── Makefile ├── README.md ├── client.cc ├── run_client_and_server.sh ├── server.cc ├── sgp.proto ├── sgp_client.cc ├── sgp_client.h ├── sgp_lite.proto ├── sgp_protocol.cc ├── sgp_protocol.h ├── sgp_server.cc └── sgp_server.h ├── images ├── Makefile ├── protocol_diagram.dot └── protocol_diagram.svg ├── include ├── Makefile ├── gep_channel.h ├── gep_channel_array.h ├── gep_client.h ├── gep_common.h ├── gep_protocol.h ├── gep_server.h ├── gep_utils.h └── mock_gep_channel.h ├── rules.mk ├── src ├── Makefile ├── gep_channel.cc ├── gep_channel_array.cc ├── gep_client.cc ├── gep_protocol.cc ├── gep_server.cc ├── raw_socket_interface.h ├── socket_interface.cc ├── socket_interface.h ├── time_manager.cc ├── time_manager.h ├── utils.cc └── utils.h └── test ├── Makefile ├── gep_channel_array_test.cc ├── gep_channel_test.cc ├── gep_client_test.cc ├── gep_end_to_end_test.cc ├── gep_protocol_test.cc ├── gep_server_test.cc ├── gep_test_lib.cc ├── gep_test_lib.h ├── mock_raw_socket_interface.h ├── mock_time_manager.h ├── socket_interface_test.cc ├── test.proto ├── test_lite.proto ├── test_protocol.cc ├── test_protocol.h └── utils_test.cc /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | asan_logs/*.log 3 | coverage_html/ 4 | .cproject 5 | *.gcda 6 | *.gcno 7 | *.info 8 | *.[oa] 9 | *.pb.cc 10 | *.pb.h 11 | .project 12 | .protos_done 13 | *.so 14 | .stamp_* 15 | *_test 16 | test/*_test 17 | test/*_test_lite 18 | example/client 19 | example/server 20 | example/client_lite 21 | example/server_lite 22 | test_logs/*.log 23 | tsan_logs/*.log 24 | -------------------------------------------------------------------------------- /CONTRIBUTING: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at the end). 2 | 3 | ### Before you contribute 4 | Before we can use your code, you must sign the 5 | [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual?csw=1) 6 | (CLA), which you can do online. The CLA is necessary mainly because you own the 7 | copyright to your changes, even after your contribution becomes part of our 8 | codebase, so we need your permission to use and distribute your code. We also 9 | need to be sure of various other things-for instance that you'll tell us if you 10 | know that your code infringes on other people's patents. You don't have to sign 11 | the CLA until after you've submitted your code for review and a member has 12 | approved it, but you must do it before we can put your code into our codebase. 13 | Before you start working on a larger contribution, you should get in touch with 14 | us first through the issue tracker with your idea so that we can help out and 15 | possibly guide you. Coordinating up front makes it much easier to avoid 16 | frustration later on. 17 | 18 | ### Code reviews 19 | All submissions, including submissions by project members, require review. We 20 | use Github pull requests for this purpose. 21 | 22 | ### The small print 23 | Contributions made by corporations are covered by a different agreement than 24 | the one above, the Software Grant and Corporate Contributor License Agreement. 25 | 26 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # People who have agreed to one of the CLAs and can contribute patches. 2 | # The AUTHORS file lists the copyright holders; this file 3 | # lists people. For example, Google employees are listed here 4 | # but not in AUTHORS, because Google holds the copyright. 5 | # 6 | # https://developers.google.com/open-source/cla/individual 7 | # https://developers.google.com/open-source/cla/corporate 8 | # 9 | # Names should be added to this file as: 10 | # Name 11 | 12 | Chema Gonzalez 13 | Chris Kuiper 14 | Jean-Francois Thibert 15 | 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2015] Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Google Inc. Apache 2.0. 2 | 3 | TOP:=. 4 | include $(TOP)/rules.mk 5 | 6 | default: all 7 | 8 | SUBDIRS=include src test example 9 | 10 | PREFIX=/usr 11 | BINDIR=$(DESTDIR)$(PREFIX)/bin 12 | LIBDIR=$(DESTDIR)$(PREFIX)/lib 13 | 14 | COV_SUBDIRS=$(filter-out include,$(SUBDIRS)) 15 | 16 | 17 | all: .protos_done $(addsuffix /all,$(SUBDIRS)) 18 | test: $(addsuffix /test,$(SUBDIRS)) 19 | test-full: $(addsuffix /test-full,$(SUBDIRS)) 20 | test-lite: $(addsuffix /test-lite,$(SUBDIRS)) 21 | tests: .protos_done $(addsuffix /tests,$(SUBDIRS)) 22 | clean:: $(addsuffix /clean,$(SUBDIRS)) 23 | install: $(addsuffix /install,$(SUBDIRS)) 24 | install-libs: $(addsuffix /install-libs,$(SUBDIRS)) 25 | 26 | test/test example/all : src/all 27 | 28 | .protos_done: test/test.proto example/sgp.proto 29 | $(MAKE) -C test test.pb.h 30 | $(MAKE) -C test test_lite.pb.h 31 | $(MAKE) -C example sgp.pb.h 32 | $(MAKE) -C example sgp_lite.pb.h 33 | 34 | %/all: 35 | $(MAKE) -C $* all 36 | 37 | %/test: 38 | $(MAKE) -C $* test 39 | 40 | %/test-full: 41 | $(MAKE) -C $* test-full 42 | 43 | %/test-lite: 44 | $(MAKE) -C $* test-lite 45 | 46 | %/tests: 47 | $(MAKE) -C $* tests 48 | 49 | %/clean: 50 | $(MAKE) -C $* clean 51 | 52 | %/install: 53 | $(MAKE) -C $* install 54 | 55 | %/install-libs: 56 | $(MAKE) -C $* install-libs 57 | 58 | # Run proper ThreadSanitizer from scratch 59 | ThreadSanitizer: 60 | rm -f $(TSAN_OUT)/*.log 61 | $(MAKE) clean 62 | $(MAKE) test-tsan 63 | 64 | # Run proper AddressSanitizer from scratch 65 | AddressSanitizer: 66 | rm -f $(ASAN_OUT)/*.log 67 | $(MAKE) clean 68 | $(MAKE) test-asan 69 | 70 | # Run proper host target coverage from scratch 71 | host-coverage: 72 | $(MAKE) clean 73 | $(MAKE) -j1 gen_cov_report 74 | 75 | # Build cross-platform image with coverage enabled 76 | cross-coverage: 77 | $(MAKE) clean 78 | $(MAKE) USE_COV=1 NO_CLANG=1 COV_OUT_DIR="/tmp/cov_libgep" 79 | 80 | # Coverage HTML output directory 81 | COV_HTML_OUT=coverage_html 82 | 83 | gen_cov_report: cov geninfo-full geninfo-lite 84 | lcov --add-tracefile libgep-full.info -a libgep-lite.info -o libgep.info 85 | $(GENHTML) -o $(COV_HTML_OUT)/ --highlight --legend -t $(COV_HTML_OUT)/ \ 86 | libgep.info 87 | 88 | geninfo-full: 89 | $(MAKE) USE_COV=1 NO_CLANG=1 test-full 90 | $(GENINFO) --gcov-tool gcov --no-external --no-recursion $(COV_SUBDIRS) \ 91 | --output-file libgep-full.info 92 | 93 | geninfo-lite: 94 | $(MAKE) USE_COV=1 NO_CLANG=1 test-lite 95 | $(GENINFO) --gcov-tool gcov --no-external --no-recursion $(COV_SUBDIRS) \ 96 | --output-file libgep-lite.info 97 | 98 | clean:: 99 | rm -rf *~ .*~ */*.pb.* *.log .protos_done 100 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GEP: A Generic, Protobuf-Based Client-Server Protocol 2 | ===================================================== 3 | 4 | 5 | Introduction 6 | ------------ 7 | 8 | GEP (Generic Event Protobuf aka Generic Eh... Protobuf) is a generic 9 | protocol to implement asynchronous, protocol buffer-based, 10 | N-clients/1-server protocols. 11 | 12 | A protocol is defined as a series of protocol buffer 13 | ([protobuf](https://developers.google.com/protocol-buffers/?hl=en)) 14 | messages that can be exchanged between a client and a server. The 15 | idea is to make it very easy for a user to create a new protocol by 16 | defining: 17 | 18 | 1. the protobuf messages that the protocol will allow passing 19 | in each direction, 20 | 2. the port where the server will listen to requests from (multiple) 21 | clients, and 22 | 3. the tags associated to the different protobuf messages. 23 | 24 |
25 | protocol_diagram 26 |
27 | Figure 1: Diagram of a GEP protocol-based client/server. 28 |
29 |
30 | 31 | GEP-based protocols are asynchronous by definition: The sender sends 32 | a protobuf message to the receiver, and it does not wait for an answer 33 | from the receiver. If your protocol needs status/return messages, 34 | they need be implemented on top of GEP. 35 | 36 | The asynchronous behavior is coherent with the operation of the main user, 37 | where one server sends and receives messages to/from multiple clients. 38 | The use of protobuf messages allows both clients and servers that keep 39 | their data structures as protobufs themselves. This makes the code 40 | extremely simple, and very easy to serialize (e.g. dump state in the 41 | logs in a systematic way). 42 | 43 | 44 | GEP Manual 45 | ---------- 46 | 47 | The best way to learn how to use GEP is by checking the provided example, 48 | called SGP. Check the [SGP Documentation](example). Note that 49 | all the `example/sgp_*` files are mostly boilerplate, so it should be 50 | easy to adapt them for your own protocol. 51 | 52 | Note that both the client and server stub classes, when started, will 53 | create a thread in charge of receiving messages and running the callback 54 | functions. 55 | 56 | In order to test the current code, you need a modern version of protobuf 57 | (This will change once proto3 is officially released). 58 | 59 | The following code installs it in a private, temporary directory. 60 | 61 | ```bash 62 | git clone https://github.com/google/protobuf 63 | cd protobuf/ 64 | ./autogen.sh 65 | PROTOBUF_PREFIX=/tmp/usr 66 | ./configure --prefix=$PROTOBUF_PREFIX 67 | make 68 | make install 69 | cd .. 70 | ``` 71 | 72 | Once you have installed it, try the libgep code: 73 | 74 | ```bash 75 | git clone https://github.com/google/libgep 76 | cd libgep/ 77 | PROTOBUF_PREFIX=/tmp/usr make 78 | PROTOBUF_PREFIX=/tmp/usr make test 79 | ``` 80 | 81 | `make` will create 4 libraries, namely `libgepclient.a`, 82 | `libgepclient-lite.a`, `libgepserver.a`, and 83 | `libgepserver-lite.a`. You also need the .h headers in 84 | the `include/` directory. 85 | 86 | 87 | 88 | GEP Operation: Protocol Definition 89 | ---------------------------------- 90 | 91 | As mentioned before, a GEP-based protocol is defined by 2 sets of 92 | protobuf messages (those that clients can send to the server, and 93 | those that the server can send to the clients), a port number, 94 | and the per-protobuf message tags. 95 | 96 | In the future most of this information can be provided by the user 97 | using something like a 98 | [grpc](http://www.grpc.io/) service, but currently the user needs 99 | to define this information in her own C++ class. 100 | 101 | The first part is to define the set of messages that clients and 102 | server can exchange to each other: 103 | 104 | 105 | $ cat examples/sgp.proto 106 | ... 107 | message Command1 { 108 | optional int64 a = 1; 109 | optional int32 b = 2; 110 | } 111 | 112 | message Command2 { 113 | optional int64 id = 1; 114 | } 115 | ... 116 | 117 | Figure 2: SGP (a GEP-based protocol) messages. 118 | 119 | 120 | Then, the user must define the protocol as a class (called SGPProtocol 121 | in this case) that derives from GepProtocol, and which implements a 122 | couple of functions that map a set of unique IDs (aka ``tags'') to 123 | the protobuf messages that can be passed back and forth between client 124 | and server. 125 | In particular, GetTag() maps a protobuf message to a tag, while 126 | GetMessage() maps a tag to a protobuf message. 127 | 128 | $ cat example/sgp_protocol.h 129 | ... 130 | class SGPProtocol : public GepProtocol { 131 | ... 132 | // supported messages 133 | static constexpr uint32_t MSG_TAG_COMMAND_1 = 134 | MakeTag('c', 'm', 'd', '1'); 135 | static constexpr uint32_t MSG_TAG_COMMAND_2 = 136 | MakeTag('c', 'm', 'd', '2'); 137 | ... 138 | // returns the tag associated to a message. 139 | virtual uint32_t GetTag(const GepProtobufMessage *msg); 140 | // constructs an object of a given type. 141 | virtual GepProtobufMessage *GetMessage(uint32_t tag); 142 | }; 143 | ... 144 | 145 | $ cat example/sgp_protocol.cc 146 | uint32_t SGPProtocol::GetTag(const GepProtobufMessage *msg) { 147 | if (dynamic_cast(msg) != NULL) 148 | return MSG_TAG_COMMAND_1; 149 | else if (dynamic_cast(msg) != NULL) 150 | return MSG_TAG_COMMAND_2; 151 | ... 152 | } 153 | 154 | GepProtobufMessage *SGPProtocol::GetMessage(uint32_t tag) { 155 | GepProtobufMessage *msg = NULL; 156 | switch (tag) { 157 | case MSG_TAG_COMMAND_1: 158 | msg = new Command1Message(); 159 | break; 160 | case MSG_TAG_COMMAND_2: 161 | msg = new Command2Message(); 162 | break; 163 | ... 164 | } 165 | return msg; 166 | } 167 | 168 | Figure 3: Implementation of SGPProtocol. 169 | 170 | 171 | GEP Operation: Client/Server Definition 172 | --------------------------------------- 173 | 174 | After defining the protocol, the user must define the client and server 175 | classes (both are very similar). The client must derive from GepClient, 176 | and the server from GepServer (these classes implement the network 177 | operations). Both must include the receiver callbacks that will be 178 | called when a message is received. 179 | 180 | Both client and server must be initialized with a virtual function table 181 | (GepVFT) that maps each tag a side wants to listen to, to the callback 182 | that must be called on receiving one. 183 | 184 | 185 | $ cat example/sgp_client.h 186 | ... 187 | class SGPClient: public GepClient { 188 | public: 189 | SGPClient(); 190 | ... 191 | 192 | // protocol object callbacks: These are object (non-static) callback 193 | // methods, which is handy for the callers. 194 | virtual bool Recv(const Command1 &msg) = 0; 195 | virtual bool Recv(const Command2 &msg) = 0; 196 | ... 197 | }; 198 | 199 | const GepVFT kSGPClientOps = { 200 | {SGPProtocol::MSG_TAG_COMMAND_1, &RecvMessage}, 201 | {SGPProtocol::MSG_TAG_COMMAND_2, &RecvMessage}, 202 | ... 203 | }; 204 | 205 | Figure 4: Implementation of SGPClient. 206 | 207 | 208 | The SGPServer implementation is very similar to that of SGPClient. The 209 | main difference is that the Recv() callbacks can have an extra parameter 210 | (``id''), identifying the client that sent the message. 211 | 212 | 213 | $ cat example/sgp_server.h 214 | ... 215 | class SGPServer: public GepServer { 216 | public: 217 | SGPServer(); 218 | ... 219 | 220 | // protocol object callbacks: These are object (non-static) callback 221 | // methods, which is handy for the callers. 222 | virtual bool Recv(const Command1 &msg, int id) = 0; 223 | virtual bool Recv(const Command2 &msg, int id) = 0; 224 | ... 225 | }; 226 | 227 | const GepVFT kSGPServerOps = { 228 | {SGPProtocol::MSG_TAG_COMMAND_1, &RecvMessageId}, 229 | {SGPProtocol::MSG_TAG_COMMAND_2, &RecvMessageId}, 230 | ... 231 | }; 232 | 233 | Figure 5: Implementation of SGPServer. 234 | 235 | 236 | Both client and server can use `Send(const Message& msg)` to send a 237 | message to the other side. 238 | 239 | 240 | GEP Implementation Details 241 | -------------------------- 242 | 243 | In the client side, a GepChannel object provides a SendMessage() 244 | function that allows sending protobuf messages to the server. It also 245 | provides a (selectable) socket where to listen for messages from the 246 | server, and a RecvData() function that creates a protobuf message 247 | and dispatches it to the corresponding callback. 248 | 249 | The GepClient object performs the obvious client reception side: 250 | select socket, receive message, dispatch it. 251 | 252 | In the server side, a GepChannelArray object provides a SendMessage() 253 | function that allows sending protobuf messages to all the clients, 254 | plus a per-client SendMessage() that sends the message to a single 255 | one. It also provides a (selectable) server socket where to listen 256 | for connections from the server, and a (bound size) vector of 257 | GepChannel objects that give access to the (selectable) sockets where 258 | to listen for messages from the different clients (and their 259 | RecvData() function). 260 | 261 | The GepServer object performs the obvious server reception side (select 262 | server and per-client sockets, create an object for new clients, 263 | receive message and dispatch it for messages). 264 | 265 | 266 | GEP Protocol 267 | ------------ 268 | 269 | Any GEP-derived protocol encodes a protobuf message in the wire using 270 | a very simple wire format: 271 | 272 | 0 1 2 3 273 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 274 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 275 | 0 | gep_id | 276 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 277 | 4 | tag | 278 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 279 | 8 | value_len | 280 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 281 | 12 | ... | 282 | | message | 283 | | ... | 284 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 285 | 286 | Figure 6: A GEP protocol packet in the wire. 287 | 288 | Where: 289 | 290 | - gep\_id: unique GEP identificator [4 bytes]. Default is `gepp` 291 | (0x67657070). 292 | - tag: tag identifying the message being sent [4 bytes]. The tags are 293 | those used to defined the protocol. 294 | - value\_len: length of the message [4 bytes]. This is the length of the 295 | packet (minus the 12-byte header). 296 | - message: a serialized protobuf message [value\_len bytes]. Default 297 | is to use a text protobuf, which allows easy debugging by reading 298 | the packets in the wire. Binary protobufs can be selected too (and 299 | are the default in the lite mode). 300 | 301 | 302 | Protobuf-Lite Support 303 | --------------------- 304 | 305 | GEP also provides a protobuf-lite version. Protobuf-lite is a light 306 | version of protobuf (the libraries are 1/10th the size of vanilla 307 | protobuf) in exchange of support for neither descriptors nor reflection. 308 | There is no support for text protobufs in lite mode, so the protocol 309 | will use binary ones in the wire. 310 | 311 | In order to use the lite version of GEP, you need to: 312 | 313 | 1. ensure you define the GEP lite flag before you include any of the 314 | GEP header files (`GEP_LITE`). 315 | 2. link with the lite version of the libraries (`libgepclient-lite.a` and 316 | `libgepserver-lite.a`) 317 | 318 | 319 | Future Work 320 | ----------- 321 | 322 | As mentioned before, currently GEP-based protocols need to adapt some 323 | amount of boilerplate (see the `example/` directory) for each new protocol. 324 | Typically, the user needs to copy the protocol, client, and server stubs 325 | from an example, and fill them with her data. 326 | 327 | In the future most of this information can be provided by the user 328 | using something like a 329 | [grpc](http://www.grpc.io/) service. 330 | 331 | 332 | -------------------------------------------------------------------------------- /example/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Google Inc. Apache 2.0. 2 | 3 | TOP:=.. 4 | TARGETS= client server client_lite server_lite 5 | 6 | # add the local gep libraries info before the hostdir ones get added 7 | CPPFLAGS+=-I../include -I../src $(PROTO_CPPFLAGS) 8 | LDFLAGS+=-L../src 9 | 10 | include ../rules.mk 11 | 12 | CPPFLAGS+=-I. -I.. -I../include 13 | 14 | all: .protos_done 15 | $(MAKE) all_for_real_this_time 16 | 17 | all_for_real_this_time: $(TARGETS) 18 | 19 | .protos_done: sgp.proto 20 | $(MAKE) sgp.pb.h 21 | $(MAKE) sgp_lite.pb.h 22 | 23 | sgp.pb.h: sgp.proto 24 | echo "Building sgp.pb.h" 25 | $(HOST_PROTOC) $(PROTOC_FLAGS) $< 26 | 27 | client: \ 28 | sgp.pb.t.o \ 29 | sgp_protocol.t.o \ 30 | sgp_client.t.o 31 | 32 | server: \ 33 | sgp.pb.t.o \ 34 | sgp_protocol.t.o \ 35 | sgp_server.t.o 36 | 37 | client : LIBS+=$(PROTOFULL_LDFLAGS) -L../src/ -lgepclient 38 | 39 | server : LIBS+=$(PROTOFULL_LDFLAGS) -L../src/ -lgepserver 40 | 41 | 42 | # lite targets 43 | sgp_lite.pb.h: sgp_lite.proto 44 | echo "Building sgp_lite.pb.h" 45 | $(HOST_PROTOC) $(PROTOC_FLAGS) $< 46 | 47 | sgp_protocol_lite.o: sgp_protocol.cc 48 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DGEP_LITE -c -o $@ $< 49 | 50 | sgp_client_lite.o: sgp_client.cc 51 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DGEP_LITE -c -o $@ $< 52 | 53 | sgp_server_lite.o: sgp_server.cc 54 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DGEP_LITE -c -o $@ $< 55 | 56 | client_lite.o: client.cc 57 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DGEP_LITE -c -o $@ $< 58 | 59 | server_lite.o: server.cc 60 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DGEP_LITE -c -o $@ $< 61 | 62 | client_lite: client_lite.o 63 | $(CXX) $(LDFLAGS) -o $@ $^ $(LIBS) 64 | 65 | server_lite: server_lite.o 66 | $(CXX) $(LDFLAGS) -o $@ $^ $(LIBS) 67 | 68 | client_lite: \ 69 | sgp_lite.pb.t.o \ 70 | sgp_protocol_lite.o \ 71 | sgp_client_lite.o 72 | 73 | server_lite: \ 74 | sgp_lite.pb.t.o \ 75 | sgp_protocol_lite.o \ 76 | sgp_server_lite.o 77 | 78 | client_lite : LIBS+=$(PROTOLITE_LDFLAGS) -L../src/ -lgepclient-lite 79 | 80 | server_lite : LIBS+=$(PROTOLITE_LDFLAGS) -L../src/ -lgepserver-lite 81 | 82 | runtests : runtests-full runtests-lite 83 | 84 | runtests-full : client server 85 | ./run_client_and_server.sh ./client ./server 86 | 87 | runtests-lite : client_lite server_lite 88 | ./run_client_and_server.sh ./client_lite ./server_lite 89 | 90 | install: 91 | 92 | clean:: 93 | rm -f *.pb.* .protos_done client server client_lite server_lite 94 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | SGP: A Simple GEP-based Protocol 2 | ================================ 3 | 4 | This directory contains an example GEP-based protocol called SGP. 5 | SGP is a simple protocol where server and clients can send to each 6 | other a set pre-defined protocol buffer objects. 7 | 8 | The directory contains the following files: 9 | 10 | * `sgp.proto`: the list of messages that can be sent (defined as protocol 11 | buffers). We also include `sgp_lite.proto`, the protobuf-lite version 12 | of the list of messages. 13 | * `sgp_client.h|cc`: the SGP client stub. It defines an abstract class that 14 | can be filled up in order to define an SGP-based client. 15 | * `sgp_server.h|cc`: the SGP server stub. It defines an abstract class that 16 | can be filled up in order to define an SGP-based server. 17 | * `sgp_protocol.h|cc`: the SGP protocol definition. It defines the default 18 | port and tags used in the wire. 19 | * `client.cc`/`server.cc`: simple client and server based on the SGP stubs. 20 | 21 | Notes: 22 | 23 | * in SGP both client(s) and server can send and receive the same protocol 24 | messages. Your protocol may have messages that are only sent in one 25 | direction. 26 | * the `sgp_client.h|cc`, `sgp_server.h|cc`, and `sgp_protocol.cc` files 27 | are almost completely boilerplate that could be generated from the 28 | `sgp_protocol.h` file. They only information the user needs to include 29 | is the list of protocol buffer messages that can be sent in each 30 | direction, the default port, and the tags associated to the different 31 | messages that can be sent. This should be relatively easy to generate 32 | automatically from an 33 | [IDL](https://en.wikipedia.org/wiki/Interface_description_language) 34 | (maybe something similar to [gRPC](http://www.grpc.io/)), which would 35 | allow autogen stubs. 36 | 37 | -------------------------------------------------------------------------------- /example/client.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // simple client 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "sgp_client.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "sgp_protocol.h" // for SGPProtocol, etc 23 | #ifndef GEP_LITE 24 | #include "sgp.pb.h" // for Command1, etc 25 | #else 26 | #include "sgp_lite.pb.h" // for Command1, etc 27 | #endif 28 | 29 | class MyClient: public SGPClient { 30 | public: 31 | MyClient(int port) 32 | : SGPClient(port) { 33 | cnt1_ = 0; 34 | cnt2_ = 0; 35 | cnt3_ = 0; 36 | cnt4_ = 0; 37 | } 38 | virtual ~MyClient() {}; 39 | 40 | // protocol callbacks 41 | bool Recv(const Command1 &msg) override; 42 | bool Recv(const Command2 &msg) override; 43 | bool Recv(const Command3 &msg) override; 44 | bool Recv(const Command4 &msg) override; 45 | 46 | std::atomic cnt1_; 47 | std::atomic cnt2_; 48 | std::atomic cnt3_; 49 | std::atomic cnt4_; 50 | int GetCnt1() { return cnt1_; } 51 | int GetCnt2() { return cnt2_; } 52 | int GetCnt3() { return cnt3_; } 53 | int GetCnt4() { return cnt4_; } 54 | }; 55 | 56 | bool MyClient::Recv(const Command1 &msg) { 57 | cnt1_++; 58 | return true; 59 | } 60 | 61 | bool MyClient::Recv(const Command2 &msg) { 62 | cnt2_++; 63 | return true; 64 | } 65 | 66 | bool MyClient::Recv(const Command3 &msg) { 67 | cnt3_++; 68 | return true; 69 | } 70 | 71 | bool MyClient::Recv(const Command4 &msg) { 72 | cnt4_++; 73 | return true; 74 | } 75 | 76 | 77 | typedef struct arg_values 78 | { 79 | char *fifo; 80 | int cnt1; 81 | int cnt2; 82 | int cnt3; 83 | int cnt4; 84 | int nrem; 85 | char** rem; 86 | } arg_values; 87 | 88 | 89 | // default values 90 | #define DEFAULT_CNT1 1 91 | #define DEFAULT_CNT2 2 92 | #define DEFAULT_CNT3 3 93 | #define DEFAULT_CNT4 4 94 | 95 | 96 | void usage(char *name) 97 | { 98 | fprintf(stderr, "usage: %s [options]\n", name); 99 | fprintf(stderr, "where options are:\n"); 100 | fprintf(stderr, "\t--fifo :\tUse for client-server " 101 | "communications\n"); 102 | fprintf(stderr, "\t--cnt1 :\tSend Command1 messages [%i]\n", 103 | DEFAULT_CNT1); 104 | fprintf(stderr, "\t--cnt2 :\tSend Command2 messages [%i]\n", 105 | DEFAULT_CNT2); 106 | fprintf(stderr, "\t--cnt3 :\tSend Command3 messages [%i]\n", 107 | DEFAULT_CNT3); 108 | fprintf(stderr, "\t--cnt4 :\tSend Command4 messages [%i]\n", 109 | DEFAULT_CNT4); 110 | fprintf(stderr, "\t-h:\t\tHelp\n"); 111 | } 112 | 113 | // For long options that have no equivalent short option, use a 114 | // non-character as a pseudo short option, starting with CHAR_MAX + 1. 115 | enum { 116 | CNT1OPTION = CHAR_MAX + 1, 117 | CNT2OPTION, 118 | CNT3OPTION, 119 | CNT4OPTION, 120 | }; 121 | 122 | 123 | arg_values *parse_args(int argc, char** argv) { 124 | int c; 125 | // getopt_long stores the option index here 126 | int optindex = 0; 127 | 128 | static arg_values values; 129 | // set default values 130 | values.cnt1 = DEFAULT_CNT1; 131 | values.cnt2 = DEFAULT_CNT2; 132 | values.cnt3 = DEFAULT_CNT3; 133 | values.cnt4 = DEFAULT_CNT4; 134 | 135 | // long options 136 | static struct option longopts[] = { 137 | // matching options to short options 138 | {"help", no_argument, NULL, 'h'}, 139 | {"fifo", required_argument, NULL, 'f'}, 140 | // options without a short option match 141 | {"cnt1", required_argument, NULL, CNT1OPTION}, 142 | {"cnt2", required_argument, NULL, CNT2OPTION}, 143 | {"cnt3", required_argument, NULL, CNT3OPTION}, 144 | {"cnt4", required_argument, NULL, CNT4OPTION}, 145 | {NULL, 0, NULL, 0} 146 | }; 147 | 148 | char *endptr; 149 | while ((c = getopt_long(argc, argv, "", longopts, &optindex)) != -1) { 150 | switch (c) { 151 | case 'f': 152 | values.fifo = optarg; 153 | break; 154 | 155 | case CNT1OPTION: 156 | values.cnt1 = strtol(optarg, &endptr, 0); 157 | if (*endptr != '\0') 158 | { 159 | usage(argv[0]); 160 | exit(-1); 161 | } 162 | break; 163 | 164 | case CNT2OPTION: 165 | values.cnt2 = strtol(optarg, &endptr, 0); 166 | if (*endptr != '\0') 167 | { 168 | usage(argv[0]); 169 | exit(-1); 170 | } 171 | break; 172 | 173 | case CNT3OPTION: 174 | values.cnt3 = strtol(optarg, &endptr, 0); 175 | if (*endptr != '\0') 176 | { 177 | usage(argv[0]); 178 | exit(-1); 179 | } 180 | break; 181 | 182 | case CNT4OPTION: 183 | values.cnt4 = strtol(optarg, &endptr, 0); 184 | if (*endptr != '\0') 185 | { 186 | usage(argv[0]); 187 | exit(-1); 188 | } 189 | break; 190 | 191 | case '?': 192 | case 'h': 193 | // getopt_long already printed an error message 194 | usage(argv[0]); 195 | exit(0); 196 | break; 197 | 198 | default: 199 | printf("Unsupported option: %c\n", c); 200 | usage(argv[0]); 201 | } 202 | } 203 | 204 | // store remaining arguments 205 | values.nrem = argc - optind; 206 | values.rem = argv + optind; 207 | 208 | return &values; 209 | } 210 | 211 | 212 | #define MAX_BUF 1024 213 | 214 | int main(int argc, char **argv) { 215 | arg_values *values; 216 | 217 | // parse args 218 | values = parse_args(argc, argv); 219 | 220 | // use the fifo to get the port number from the server 221 | int fd = open(values->fifo, O_RDWR); 222 | char rbuf[MAX_BUF]; 223 | int len = read(fd, rbuf, MAX_BUF); 224 | rbuf[len] = '\0'; 225 | close(fd); 226 | char *endptr; 227 | int port = strtol(rbuf, &endptr, 0); 228 | if (*endptr != '\0') { 229 | printf("client: did not get a valid port number: \"%s\"\n", rbuf); 230 | exit(-1); 231 | } 232 | printf("client: will use port %i\n", port); 233 | 234 | // create the client 235 | std::unique_ptr my_client; 236 | my_client.reset(new MyClient(port)); 237 | 238 | // start the client 239 | int tries = 0; 240 | while (my_client->Start() < 0) { 241 | if (++tries > 3) { 242 | fprintf(stderr, "error: cannot start client (tried %d times)\n", tries); 243 | return -1; 244 | } 245 | sleep(1); 246 | } 247 | 248 | // send some messages 249 | Command1 cmd1; 250 | for (int i = 0; i < values->cnt1; ++i) 251 | my_client->Send(cmd1); 252 | Command2 cmd2; 253 | for (int i = 0; i < values->cnt2; ++i) 254 | my_client->Send(cmd2); 255 | Command3 cmd3; 256 | for (int i = 0; i < values->cnt3; ++i) 257 | my_client->Send(cmd3); 258 | Command4 cmd4; 259 | for (int i = 0; i < values->cnt4; ++i) 260 | my_client->Send(cmd4); 261 | 262 | // give some time for all both sides to finish 263 | usleep(3000000); 264 | 265 | printf("results: %i %i %i %i\n", my_client->GetCnt1(), my_client->GetCnt2(), 266 | my_client->GetCnt3(), my_client->GetCnt4()); 267 | 268 | my_client->Stop(); 269 | 270 | return 0; 271 | } 272 | -------------------------------------------------------------------------------- /example/run_client_and_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright Google Inc. Apache 2.0. 4 | 5 | # ensure 2 arguments 6 | if [[ "$#" -lt "2" ]]; then 7 | echo "error: need 1 parameters ($0 )" 8 | exit -1 9 | fi 10 | 11 | 12 | CLIENT="$1" 13 | shift 14 | SERVER="$1" 15 | 16 | # use a random name for a fifo to communicate client and server 17 | tmpfifo=$(mktemp -u /tmp/tmp.fifo.XXXXXXXX) 18 | mkfifo "${tmpfifo}" 19 | 20 | sout=$(${SERVER} --fifo ${tmpfifo} --cnt1 44 --cnt2 33 --cnt3 22 --cnt4 11) & 21 | cout=$(${CLIENT} --fifo ${tmpfifo}) 22 | 23 | rm "${tmpfifo}" 24 | 25 | # ensure the client returns the right result 26 | expected_client_output="results: 44 33 22 11" 27 | if [[ "$cout" == *"$expected_client_output"* ]]; then 28 | echo "OK" 29 | exit 0 30 | else 31 | echo "error: cout is ${cout}" 32 | exit -1 33 | fi 34 | 35 | -------------------------------------------------------------------------------- /example/server.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // simple server 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "sgp_server.h" 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include "sgp_protocol.h" // for SGPProtocol, etc 23 | #ifndef GEP_LITE 24 | #include "sgp.pb.h" // for Command1, etc 25 | #else 26 | #include "sgp_lite.pb.h" // for Command1, etc 27 | #endif 28 | 29 | class MyServer: public SGPServer { 30 | public: 31 | MyServer(int port) 32 | : SGPServer(16, port) { 33 | cnt1_ = 0; 34 | cnt2_ = 0; 35 | cnt3_ = 0; 36 | cnt4_ = 0; 37 | seen_client_ = false; 38 | } 39 | virtual ~MyServer() {}; 40 | 41 | // protocol callbacks 42 | bool Recv(const Command1 &msg, int id) override; 43 | bool Recv(const Command2 &msg, int id) override; 44 | bool Recv(const Command3 &msg, int id) override; 45 | bool Recv(const Command4 &msg, int id) override; 46 | 47 | int cnt1_; 48 | int cnt2_; 49 | int cnt3_; 50 | int cnt4_; 51 | 52 | std::atomic seen_client_; 53 | }; 54 | 55 | bool MyServer::Recv(const Command1 &msg, int id) { 56 | cnt1_++; 57 | seen_client_ = true; 58 | return true; 59 | } 60 | 61 | bool MyServer::Recv(const Command2 &msg, int id) { 62 | cnt2_++; 63 | seen_client_ = true; 64 | return true; 65 | } 66 | 67 | bool MyServer::Recv(const Command3 &msg, int id) { 68 | cnt3_++; 69 | seen_client_ = true; 70 | return true; 71 | } 72 | 73 | bool MyServer::Recv(const Command4 &msg, int id) { 74 | cnt4_++; 75 | seen_client_ = true; 76 | return true; 77 | } 78 | 79 | 80 | typedef struct arg_values 81 | { 82 | char *fifo; 83 | int cnt1; 84 | int cnt2; 85 | int cnt3; 86 | int cnt4; 87 | int nrem; 88 | char** rem; 89 | } arg_values; 90 | 91 | 92 | // default values 93 | #define DEFAULT_CNT1 1 94 | #define DEFAULT_CNT2 2 95 | #define DEFAULT_CNT3 3 96 | #define DEFAULT_CNT4 4 97 | 98 | 99 | void usage(char *name) 100 | { 101 | fprintf(stderr, "usage: %s [options]\n", name); 102 | fprintf(stderr, "where options are:\n"); 103 | fprintf(stderr, "\t--fifo :\tUse for client-server " 104 | "communications\n"); 105 | fprintf(stderr, "\t--cnt1 :\tSend Command1 messages [%i]\n", 106 | DEFAULT_CNT1); 107 | fprintf(stderr, "\t--cnt2 :\tSend Command2 messages [%i]\n", 108 | DEFAULT_CNT2); 109 | fprintf(stderr, "\t--cnt3 :\tSend Command3 messages [%i]\n", 110 | DEFAULT_CNT3); 111 | fprintf(stderr, "\t--cnt4 :\tSend Command4 messages [%i]\n", 112 | DEFAULT_CNT4); 113 | fprintf(stderr, "\t-h:\t\tHelp\n"); 114 | } 115 | 116 | // For long options that have no equivalent short option, use a 117 | // non-character as a pseudo short option, starting with CHAR_MAX + 1. 118 | enum { 119 | CNT1OPTION = CHAR_MAX + 1, 120 | CNT2OPTION, 121 | CNT3OPTION, 122 | CNT4OPTION, 123 | }; 124 | 125 | 126 | arg_values *parse_args(int argc, char** argv) { 127 | int c; 128 | // getopt_long stores the option index here 129 | int optindex = 0; 130 | 131 | static arg_values values; 132 | // set default values 133 | values.cnt1 = DEFAULT_CNT1; 134 | values.cnt2 = DEFAULT_CNT2; 135 | values.cnt3 = DEFAULT_CNT3; 136 | values.cnt4 = DEFAULT_CNT4; 137 | 138 | // long options 139 | static struct option longopts[] = { 140 | // matching options to short options 141 | {"help", no_argument, NULL, 'h'}, 142 | {"fifo", required_argument, NULL, 'f'}, 143 | // options without a short option match 144 | {"cnt1", required_argument, NULL, CNT1OPTION}, 145 | {"cnt2", required_argument, NULL, CNT2OPTION}, 146 | {"cnt3", required_argument, NULL, CNT3OPTION}, 147 | {"cnt4", required_argument, NULL, CNT4OPTION}, 148 | {NULL, 0, NULL, 0} 149 | }; 150 | 151 | char *endptr; 152 | while ((c = getopt_long(argc, argv, "", longopts, &optindex)) != -1) { 153 | switch (c) { 154 | case 'f': 155 | values.fifo = optarg; 156 | break; 157 | 158 | case CNT1OPTION: 159 | values.cnt1 = strtol(optarg, &endptr, 0); 160 | if (*endptr != '\0') 161 | { 162 | usage(argv[0]); 163 | exit(-1); 164 | } 165 | break; 166 | 167 | case CNT2OPTION: 168 | values.cnt2 = strtol(optarg, &endptr, 0); 169 | if (*endptr != '\0') 170 | { 171 | usage(argv[0]); 172 | exit(-1); 173 | } 174 | break; 175 | 176 | case CNT3OPTION: 177 | values.cnt3 = strtol(optarg, &endptr, 0); 178 | if (*endptr != '\0') 179 | { 180 | usage(argv[0]); 181 | exit(-1); 182 | } 183 | break; 184 | 185 | case CNT4OPTION: 186 | values.cnt4 = strtol(optarg, &endptr, 0); 187 | if (*endptr != '\0') 188 | { 189 | usage(argv[0]); 190 | exit(-1); 191 | } 192 | break; 193 | 194 | case '?': 195 | case 'h': 196 | // getopt_long already printed an error message 197 | usage(argv[0]); 198 | exit(0); 199 | break; 200 | 201 | default: 202 | printf("Unsupported option: %c\n", c); 203 | usage(argv[0]); 204 | } 205 | } 206 | 207 | // store remaining arguments 208 | values.nrem = argc - optind; 209 | values.rem = argv + optind; 210 | 211 | return &values; 212 | } 213 | 214 | #define MAX_BUF 1024 215 | 216 | int main(int argc, char **argv) { 217 | arg_values *values; 218 | 219 | // parse args 220 | values = parse_args(argc, argv); 221 | 222 | // create the server 223 | std::unique_ptr my_server; 224 | my_server.reset(new MyServer(0)); 225 | 226 | // start the server 227 | if (my_server->Start() < 0) { 228 | fprintf(stderr, "error: cannot start server\n"); 229 | return -1; 230 | } 231 | 232 | // get the port number 233 | int port = my_server->GetProto()->GetPort(); 234 | fprintf(stdout, "server listening in port %i\n", port); 235 | 236 | // tell the client about the port 237 | char wbuf[MAX_BUF]; 238 | sprintf(wbuf, "%i", port); 239 | int fd = open(values->fifo, O_WRONLY); 240 | write(fd, wbuf, strlen(wbuf)); 241 | close(fd); 242 | 243 | // wait until we see a client message 244 | while (!my_server->seen_client_) 245 | std::this_thread::yield(); 246 | 247 | // send some messages 248 | Command1 cmd1; 249 | for (int i = 0; i < values->cnt1; ++i) 250 | my_server->Send(cmd1); 251 | Command2 cmd2; 252 | for (int i = 0; i < values->cnt2; ++i) 253 | my_server->Send(cmd2); 254 | Command3 cmd3; 255 | for (int i = 0; i < values->cnt3; ++i) 256 | my_server->Send(cmd3); 257 | Command4 cmd4; 258 | for (int i = 0; i < values->cnt4; ++i) 259 | my_server->Send(cmd4); 260 | 261 | // give some time for all both sides to finish 262 | usleep(3000000); 263 | 264 | printf("results: %i %i %i %i\n", my_server->cnt1_, my_server->cnt2_, 265 | my_server->cnt3_, my_server->cnt4_); 266 | 267 | my_server->Stop(); 268 | return 0; 269 | } 270 | -------------------------------------------------------------------------------- /example/sgp.proto: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // A protocol buffer representation of an example API. 4 | 5 | syntax = "proto2"; 6 | 7 | 8 | message Command1 { 9 | optional int64 a = 1; 10 | optional int32 b = 2; 11 | } 12 | 13 | message Command2 { 14 | optional int64 id = 1; 15 | } 16 | 17 | message Command3 { 18 | optional int64 id = 1; 19 | } 20 | 21 | message Command4 { 22 | optional int64 id = 1; 23 | enum Command { 24 | COMMAND_40 = 0; 25 | COMMAND_41 = 1; 26 | COMMAND_42 = 2; 27 | } 28 | optional Command command = 2 [default = COMMAND_40]; 29 | } 30 | -------------------------------------------------------------------------------- /example/sgp_client.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP/SGP protocol: client implementation. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "sgp_client.h" 15 | 16 | #include // for GepClient 17 | 18 | #include "sgp_protocol.h" // for SGPProtocol, etc 19 | #ifndef GEP_LITE 20 | #include "sgp.pb.h" // for Command1, etc 21 | #else 22 | #include "sgp_lite.pb.h" // for Command1, etc 23 | #endif 24 | 25 | SGPClient::SGPClient() 26 | : GepClient("sgp_client", 27 | reinterpret_cast(this), 28 | new SGPProtocol(), 29 | &kSGPClientOps) { 30 | } 31 | 32 | SGPClient::SGPClient(int port) 33 | : GepClient("sgp_client", 34 | reinterpret_cast(this), 35 | new SGPProtocol(port), 36 | &kSGPClientOps) { 37 | } 38 | 39 | SGPClient::~SGPClient() { 40 | } 41 | -------------------------------------------------------------------------------- /example/sgp_client.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // SGP protocol: client side. 4 | 5 | #ifndef _SGP_CLIENT_H_ 6 | #define _SGP_CLIENT_H_ 7 | 8 | #include // for GepClient 9 | #include // for GepVFT 10 | #include // for RecvMessage 11 | 12 | #include "sgp_protocol.h" // for SGPProtocol, etc 13 | #ifndef GEP_LITE 14 | #include "sgp.pb.h" // for Command1, etc 15 | #else 16 | #include "sgp_lite.pb.h" // for Command1, etc 17 | #endif 18 | 19 | // Class implementing a SGPClient: This is basically a GEP client 20 | // that hardcodes the use of a SGPProtocol. 21 | class SGPClient: public GepClient { 22 | public: 23 | SGPClient(); 24 | explicit SGPClient(int port); 25 | virtual ~SGPClient(); 26 | 27 | // protocol object callbacks: These are object (non-static) callback 28 | // methods, which is handy for the callers. 29 | virtual bool Recv(const Command1 &msg) = 0; 30 | virtual bool Recv(const Command2 &msg) = 0; 31 | virtual bool Recv(const Command3 &msg) = 0; 32 | virtual bool Recv(const Command4 &msg) = 0; 33 | }; 34 | 35 | const GepVFT kSGPClientOps = { 36 | {SGPProtocol::MSG_TAG_COMMAND_1, &RecvMessage}, 37 | {SGPProtocol::MSG_TAG_COMMAND_2, &RecvMessage}, 38 | {SGPProtocol::MSG_TAG_COMMAND_3, &RecvMessage}, 39 | {SGPProtocol::MSG_TAG_COMMAND_4, &RecvMessage}, 40 | }; 41 | 42 | #endif // _SGP_CLIENT_H_ 43 | -------------------------------------------------------------------------------- /example/sgp_lite.proto: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // A protocol buffer representation of an example API. 4 | 5 | syntax = "proto2"; 6 | 7 | option optimize_for = LITE_RUNTIME; 8 | 9 | message Command1 { 10 | optional int64 a = 1; 11 | optional int32 b = 2; 12 | } 13 | 14 | message Command2 { 15 | optional int64 id = 1; 16 | } 17 | 18 | message Command3 { 19 | optional int64 id = 1; 20 | } 21 | 22 | message Command4 { 23 | optional int64 id = 1; 24 | enum Command { 25 | COMMAND_40 = 0; 26 | COMMAND_41 = 1; 27 | COMMAND_42 = 2; 28 | } 29 | optional Command command = 2 [default = COMMAND_40]; 30 | } 31 | -------------------------------------------------------------------------------- /example/sgp_protocol.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP/SGP protocol implementation. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "sgp_protocol.h" 15 | 16 | #include // for NULL 17 | #include // for GepProtobufMessage 18 | 19 | #ifndef GEP_LITE 20 | #include "sgp.pb.h" // for Command1, etc 21 | #else 22 | #include "sgp_lite.pb.h" // for Command1, etc 23 | #endif 24 | 25 | 26 | SGPProtocol::SGPProtocol(int port) 27 | : GepProtocol(port) { 28 | } 29 | 30 | // TODO(chema): we must redefine these constants because the compiler is 31 | // not smart enough to treat SGPProtocol::MSG_TAG_* as the corresponding 32 | // literal 33 | // \ref http://stackoverflow.com/questions/22867654/enum-vs-constexpr-for-actual-static-constants-inside-classes 34 | constexpr uint32_t SGPProtocol::MSG_TAG_COMMAND_1; 35 | constexpr uint32_t SGPProtocol::MSG_TAG_COMMAND_2; 36 | constexpr uint32_t SGPProtocol::MSG_TAG_COMMAND_3; 37 | constexpr uint32_t SGPProtocol::MSG_TAG_COMMAND_4; 38 | 39 | uint32_t SGPProtocol::GetTag(const GepProtobufMessage *msg) { 40 | // TODO(chema): use send VFT map here instead of listing all the cases (?) 41 | if (dynamic_cast(msg) != NULL) 42 | return MSG_TAG_COMMAND_1; 43 | else if (dynamic_cast(msg) != NULL) 44 | return MSG_TAG_COMMAND_2; 45 | else if (dynamic_cast(msg) != NULL) 46 | return MSG_TAG_COMMAND_3; 47 | else if (dynamic_cast(msg) != NULL) 48 | return MSG_TAG_COMMAND_4; 49 | return 0; 50 | } 51 | 52 | GepProtobufMessage *SGPProtocol::GetMessage(uint32_t tag) { 53 | GepProtobufMessage *msg = NULL; 54 | switch (tag) { 55 | case MSG_TAG_COMMAND_1: 56 | msg = new Command1(); 57 | break; 58 | case MSG_TAG_COMMAND_2: 59 | msg = new Command2(); 60 | break; 61 | case MSG_TAG_COMMAND_3: 62 | msg = new Command3(); 63 | break; 64 | case MSG_TAG_COMMAND_4: 65 | msg = new Command4(); 66 | break; 67 | } 68 | return msg; 69 | } 70 | -------------------------------------------------------------------------------- /example/sgp_protocol.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP/SGP protocol 4 | // 5 | // SGP is a GEP-based protocol. 6 | // 7 | 8 | #ifndef _SGP_PROTOCOL_H_ 9 | #define _SGP_PROTOCOL_H_ 10 | 11 | #include // for GepProtobufMessage 12 | #include // for MakeTag, GepProtocol 13 | #include // for uint32_t 14 | 15 | 16 | // SGP protocol 17 | class SGPProtocol : public GepProtocol { 18 | public: 19 | explicit SGPProtocol(int port = kPort); 20 | virtual ~SGPProtocol() {} 21 | 22 | // basic protocol constants 23 | static const int kPort = 3456; 24 | 25 | // supported messages 26 | static constexpr uint32_t MSG_TAG_COMMAND_1 = 27 | MakeTag('c', 'm', 'd', '1'); 28 | static constexpr uint32_t MSG_TAG_COMMAND_2 = 29 | MakeTag('c', 'm', 'd', '2'); 30 | static constexpr uint32_t MSG_TAG_COMMAND_3 = 31 | MakeTag('c', 'm', 'd', '3'); 32 | static constexpr uint32_t MSG_TAG_COMMAND_4 = 33 | MakeTag('c', 'm', 'd', '4'); 34 | static constexpr uint32_t MSG_TAG_CONTROL = 35 | MakeTag('c', 't', 'r', 'l'); 36 | 37 | // returns the tag associated to a message. 38 | virtual uint32_t GetTag(const GepProtobufMessage *msg); 39 | // constructs an object of a given type. 40 | virtual GepProtobufMessage *GetMessage(uint32_t tag); 41 | }; 42 | 43 | #endif // _SGP_PROTOCOL_H_ 44 | -------------------------------------------------------------------------------- /example/sgp_server.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP/SGP protocol: server side. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "sgp_server.h" 15 | 16 | #include 17 | 18 | #include "sgp_protocol.h" // for SGPProtocol, etc 19 | #ifndef GEP_LITE 20 | #include "sgp.pb.h" // for Command1, etc 21 | #else 22 | #include "sgp_lite.pb.h" // for Command1, etc 23 | #endif 24 | 25 | 26 | SGPServer::SGPServer(int max_channel_num) 27 | : GepServer("sgp_server", 28 | max_channel_num, 29 | reinterpret_cast(this), 30 | new SGPProtocol(), 31 | &kSGPServerOps) { 32 | } 33 | 34 | SGPServer::SGPServer(int max_channel_num, int port) 35 | : GepServer("sgp_server", 36 | max_channel_num, 37 | reinterpret_cast(this), 38 | new SGPProtocol(port), 39 | &kSGPServerOps) { 40 | } 41 | 42 | SGPServer::~SGPServer() { 43 | } 44 | -------------------------------------------------------------------------------- /example/sgp_server.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // SGP protocol: server side. 4 | 5 | #ifndef _SGP_SERVER_H_ 6 | #define _SGP_SERVER_H_ 7 | 8 | #include // for GepVFT 9 | #include // for GepServer 10 | #include // for RecvMessage 11 | 12 | #include "sgp_protocol.h" // for SGPProtocol, etc 13 | #ifndef GEP_LITE 14 | #include "sgp.pb.h" // for Command1, etc 15 | #else 16 | #include "sgp_lite.pb.h" // for Command1, etc 17 | #endif 18 | 19 | // Class implementing a SGPServer: This is basically a GEP server 20 | // that hardcodes the use of a SGPProtocol. It is used as the 21 | // context in callbacks. 22 | class SGPServer: public GepServer { 23 | public: 24 | explicit SGPServer(int max_channel_num); 25 | SGPServer(int max_channel_num, int port); 26 | virtual ~SGPServer(); 27 | 28 | // protocol object callbacks: These are object (non-static) callback 29 | // methods, which is handy for the callers. 30 | virtual bool Recv(const Command1 &msg, int id) = 0; 31 | virtual bool Recv(const Command2 &msg, int id) = 0; 32 | virtual bool Recv(const Command3 &msg, int id) = 0; 33 | virtual bool Recv(const Command4 &msg, int id) = 0; 34 | }; 35 | 36 | const GepVFT kSGPServerOps = { 37 | {SGPProtocol::MSG_TAG_COMMAND_1, &RecvMessageId}, 38 | {SGPProtocol::MSG_TAG_COMMAND_2, &RecvMessageId}, 39 | {SGPProtocol::MSG_TAG_COMMAND_3, &RecvMessageId}, 40 | {SGPProtocol::MSG_TAG_COMMAND_4, &RecvMessageId}, 41 | }; 42 | 43 | #endif // _SGP_SERVER_H_ 44 | -------------------------------------------------------------------------------- /images/Makefile: -------------------------------------------------------------------------------- 1 | TOP:=.. 2 | include ../rules.mk 3 | 4 | all: 5 | 6 | install: 7 | 8 | diagram: protocol_diagram.dot 9 | dot protocol_diagram.dot -Tsvg > protocol_diagram.svg 10 | 11 | clean:: 12 | rm -f *.pb.* .protos_done 13 | -------------------------------------------------------------------------------- /images/protocol_diagram.dot: -------------------------------------------------------------------------------- 1 | digraph G { 2 | rankdir=LR; 3 | ClientAPI [shape=box, label="Send\(const Protobuf_1 &msg)\n...\nSend\(const Protobuf_n-1 &msg)\nRecv\(const Protobuf_n &msg)\n...\nRecv\(const Protobuf_m &msg)"]; 4 | ClientAPI -> client; 5 | client -> ClientAPI; 6 | client -> server [label="protobuf\nmessages"]; 7 | server -> client; 8 | ServerAPI [shape=box, label="Send\(const Protobuf_n &msg[, int id])\n...\nSend\(const Protobuf_m &msg[, int id])\nRecv\(const Protobuf_1 &msg, int id)\n...\nRecv\(const Protobuf_n-1 &msg, int id)"]; 9 | ServerAPI -> server; 10 | server -> ServerAPI; 11 | } 12 | 13 | -------------------------------------------------------------------------------- /images/protocol_diagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | G 11 | 12 | 13 | ClientAPI 14 | 15 | Send(const Protobuf_1 &msg) 16 | ... 17 | Send(const Protobuf_n-1 &msg) 18 | Recv(const Protobuf_n &msg) 19 | ... 20 | Recv(const Protobuf_m &msg) 21 | 22 | 23 | client 24 | 25 | client 26 | 27 | 28 | ClientAPI->client 29 | 30 | 31 | 32 | 33 | client->ClientAPI 34 | 35 | 36 | 37 | 38 | server 39 | 40 | server 41 | 42 | 43 | client->server 44 | 45 | 46 | protobuf 47 | messages 48 | 49 | 50 | server->client 51 | 52 | 53 | 54 | 55 | ServerAPI 56 | 57 | Send(const Protobuf_n &msg[, int id]) 58 | ... 59 | Send(const Protobuf_m &msg[, int id]) 60 | Recv(const Protobuf_1 &msg, int id) 61 | ... 62 | Recv(const Protobuf_n-1 &msg, int id) 63 | 64 | 65 | server->ServerAPI 66 | 67 | 68 | 69 | 70 | ServerAPI->server 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /include/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Google Inc. Apache 2.0. 2 | 3 | TOP:=.. 4 | include ../rules.mk 5 | 6 | all: $(TARGETS) 7 | 8 | install: 9 | $(INSTALL) -D -m 0444 \ 10 | gep_common.h \ 11 | gep_server.h \ 12 | gep_protocol.h \ 13 | gep_client.h \ 14 | gep_channel.h \ 15 | gep_channel_array.h \ 16 | gep_utils.h \ 17 | $(DESTDIR)/usr/include/ 18 | -------------------------------------------------------------------------------- /include/gep_channel.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP Protocol: GEPChannel. 4 | 5 | #ifndef _GEP_CHANNEL_H_ 6 | #define _GEP_CHANNEL_H_ 7 | 8 | #include 9 | #include // for uint32_t, uint8_t 10 | #include // for string 11 | 12 | #include "gep_common.h" // for GepProtobufMessage 13 | #include "gep_protocol.h" // for GepProtocol (ptr only), etc 14 | 15 | class SocketInterface; 16 | 17 | 18 | // Class used to manage a communication channel where protobuf messages can 19 | // be sent back and forth. 20 | class GepChannel { 21 | public: 22 | GepChannel(int id, const std::string &name, GepProtocol *proto, 23 | const GepVFT *ops, void *context, int socket = -1); 24 | virtual ~GepChannel(); 25 | 26 | // Process received event data: execute any commands and remove completed 27 | // commands from the recv buffer. 28 | // Any leftover data is moved to beginning of the buffer. If the host data 29 | // contains several commands, all of them are processed. 30 | // Returns 0 for success, -1 on a fatal error, and -2 if the connection was 31 | // closed. 32 | int RecvData(); 33 | 34 | // Send a specific protobuf message to a GEP client. 35 | // Returns status value (0 if ok, -1 for error) 36 | virtual int SendMessage(const GepProtobufMessage &msg); 37 | 38 | // socket opening/closing 39 | int OpenClientSocket(); 40 | int Close(); 41 | 42 | // accessors 43 | int GetId() const { return id_; } 44 | int GetSocket(); 45 | void SetSocket(int socket); 46 | bool IsOpenSocket(); 47 | void *GetContext() const { return context_; } 48 | int GetLen() const { return len_; } 49 | void SetLen(int len) { len_ = len; } 50 | 51 | // socket interface 52 | SocketInterface *GetSocketInterface() { return socket_interface_; } 53 | void SetSocketInterface(SocketInterface *socket_interface) { 54 | socket_interface_ = socket_interface; 55 | } 56 | 57 | // some constants 58 | // maximum time to wait for a send 59 | static const int64_t kGepSendTimeoutMs = 5; 60 | 61 | protected: 62 | // return values used by GepChannel::RecvData() 63 | enum Result { 64 | CMD_ERROR = -1, // unsupported command or invalid channel pointer 65 | CMD_OK = 0, // successfully processed one or more commands 66 | CMD_FRAGMENTED = 1, // command is fragmented, needs more data 67 | CMD_DROPPED = 2 // unsupported command, data dropped 68 | }; 69 | bool IsRecoverable(Result ret) { return ret >= 0; } 70 | 71 | private: 72 | // sends generic data to the GEP channel socket 73 | int SendData(const char *buf, int bytes); 74 | // receives generic data in the GEP channel socket 75 | Result RecvString(); 76 | // receives a TLV tuple in the GEP channel socket 77 | Result RecvTLV(uint32_t tag, int value_len, const uint8_t *value); 78 | // sends a TLV tuple to the GEP channel socket 79 | int SendTLV(uint32_t tag, int value_len, const char *value); 80 | // sends a generic string to the GEP channel socket 81 | int SendString(uint32_t tag, const std::string &s); 82 | 83 | std::string name_; 84 | GepProtocol *proto_; // not owned 85 | const GepVFT *ops_; // VFT for the receiving side (not owned) 86 | void *context_; // link to context (not owned) 87 | int id_; 88 | SocketInterface *socket_interface_; // socket interface 89 | int socket_; // command socket used to talk to the other side 90 | int len_; // amount of data currently in buf 91 | uint8_t buf_[GepProtocol::kMaxMsgLen]; // receive buffer for command data 92 | // from clients 93 | std::mutex socket_lock_; // guards access to channel socket between senders 94 | // and the socket controller (open/close) (which 95 | // is also the recv) 96 | 97 | // do not copy this object 98 | GepChannel(const GepChannel&) = delete; // suppress copy 99 | GepChannel& operator=(const GepChannel&) = delete; // suppress assignment 100 | }; 101 | 102 | #endif // _GEP_CHANNEL_H_ 103 | -------------------------------------------------------------------------------- /include/gep_channel_array.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP protocol: server's array of GepChannel. 4 | 5 | #ifndef _GEP_CHANNEL_ARRAY_H_ 6 | #define _GEP_CHANNEL_ARRAY_H_ 7 | 8 | #include // for shared_ptr 9 | #include // for mutex 10 | #include // for string 11 | #include // for fd_set 12 | #include // for vector 13 | 14 | #include "gep_channel.h" // for GepChannel 15 | #include "gep_common.h" // for GepProtobufMessage 16 | #include "gep_protocol.h" // for GepProtocol (ptr only), etc 17 | 18 | class GepServer; 19 | class SocketInterface; 20 | 21 | // Class used to manage an array of communication channels where protobuf 22 | // messages can be sent back and forth. 23 | class GepChannelArray { 24 | public: 25 | GepChannelArray(const std::string &name, GepServer *server, 26 | GepProtocol *proto, int max_channels, 27 | const GepVFT *ops, void *context); 28 | virtual ~GepChannelArray(); 29 | 30 | int OpenServerSocket(); 31 | int AcceptConnection(); 32 | int Stop(); 33 | 34 | // Send a specific protobuf message to all GEP clients. 35 | // Returns status value (0 if all ok, -1 if any of the receivers failed). 36 | // 37 | // Note that the current GEP implementation assumes the protocol has 38 | // non-idempotent (zero-or-one) execution semantics. A message sent 39 | // may or may not arrive to the receiver. We provide an error when it 40 | // does not (both for the server and the client(s)). 41 | // 42 | // If the client needs to implement idempotent requests, it can use the 43 | // error detection and do it itself. 44 | // 45 | // The server is definitely not prepared yet to offer idempotence: That 46 | // would require identifying the clients, and keeping track of which 47 | // messages have been received and which not. We expect the protocols 48 | // implemented on top of GEP to not require idempotence in the 49 | // server-initiated operations. 50 | int SendMessage(const GepProtobufMessage &msg); 51 | 52 | // Send a specific protobuf message to a specified GEP client. 53 | // Returns status value (0 if all ok, -1 if the receiver failed). 54 | int SendMessage(const GepProtobufMessage &msg, int id); 55 | 56 | void ClearGepChannelVector(); 57 | // accessors 58 | int GetVectorSize(); 59 | int GetVectorSocket(int i); 60 | int GetClientId(int i); 61 | 62 | // network management 63 | int GetServerSocket() const { return server_socket_; } 64 | void GetVectorReadFds(int *max_fds, fd_set *read_fds); 65 | void RecvData(fd_set *read_fds); 66 | 67 | // socket interface 68 | SocketInterface *GetSocketInterface() { return socket_interface_; } 69 | void SetSocketInterface(SocketInterface *socket_interface) { 70 | socket_interface_ = socket_interface; 71 | } 72 | 73 | private: 74 | int AddChannel(int socket); 75 | 76 | std::string name_; 77 | GepServer *server_; // not owned 78 | GepProtocol *proto_; // not owned 79 | const GepVFT *ops_; // VFT for the receiving side (not owned) 80 | void *context_; // link to context (not owned) 81 | int max_channels_; 82 | int last_channel_id_; 83 | // GEP channel vector (one per client) 84 | std::vector> gep_channel_vector_; 85 | // mutex to protect gep_channel_vector_ 86 | std::recursive_mutex gep_channel_vector_lock_; 87 | 88 | SocketInterface *socket_interface_; 89 | // socket accepting conns for new ctrl channels 90 | int server_socket_; 91 | }; 92 | 93 | #endif // _GEP_CHANNEL_ARRAY_H_ 94 | -------------------------------------------------------------------------------- /include/gep_client.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP protocol: client side. 4 | 5 | #ifndef _GEP_CLIENT_H_ 6 | #define _GEP_CLIENT_H_ 7 | 8 | #include // for atomic 9 | #include // for string 10 | #include // for thread 11 | 12 | #include "gep_channel.h" // for GepChannel 13 | #include "gep_common.h" // for GepProtobufMessage 14 | #include "gep_protocol.h" // for GepProtocol 15 | 16 | 17 | class GepClient { 18 | public: 19 | // Creates a GEP client instance. The instance supports connecting to 20 | // 1 GEP server and (when started via Start()) kicks off a 21 | // thread that connects on the provided TCP port to the GEP 22 | // server (at localhost). 23 | GepClient(const std::string &name, void *context, 24 | GepProtocol *proto, const GepVFT* ops); 25 | virtual ~GepClient(); 26 | 27 | // Starts the given GEP client by creating the service thread and 28 | // connecting to the GEP server. 29 | // Returns an error code (0 if ok, <0 if problems) 30 | virtual int Start(); 31 | // Stop the given GEP client by terminating the thread 32 | // and closing the GEP channel connections. 33 | // Returns an error code (0 if ok, <0 if problems) 34 | virtual void Stop(); 35 | 36 | // default function run by the server thread 37 | virtual void RunThread(); 38 | 39 | // accessors 40 | GepProtocol *GetProto() { return proto_; } 41 | GepChannel *GetGepChannel() { return gep_channel_; } 42 | std::atomic &GetThreadCtrl() { return thread_ctrl_; } 43 | 44 | // send API 45 | // Returns status value (0 if all ok, -1 for any error) 46 | virtual int Send(const GepProtobufMessage &msg); 47 | 48 | // Returns how many times the client reconnected to the server socket. 49 | int GetReconnectCount() { return reconnect_count_; } 50 | 51 | private: 52 | // Attempts to reconnect the socket when disconnected. 53 | void Reconnect(); 54 | 55 | std::string name_; 56 | void *context_; // not owned 57 | GepProtocol *proto_; // owned and responsible for destruction 58 | const GepVFT* ops_; // not owned 59 | GepChannel *gep_channel_; // owned 60 | std::thread thread_; 61 | std::atomic thread_ctrl_; 62 | std::atomic reconnect_count_; 63 | }; 64 | 65 | #endif // _GEP_CLIENT_H_ 66 | -------------------------------------------------------------------------------- /include/gep_common.h: -------------------------------------------------------------------------------- 1 | // GEP protocol: common. 2 | 3 | #ifndef _GEP_COMMON_H_ 4 | #define _GEP_COMMON_H_ 5 | 6 | #ifndef GEP_LITE 7 | # include // for Message 8 | # define GepProtobufMessage ::google::protobuf::Message 9 | #else 10 | # include // for MessageLite 11 | # define GepProtobufMessage ::google::protobuf::MessageLite 12 | #endif 13 | 14 | #endif // _GEP_COMMON_H_ 15 | -------------------------------------------------------------------------------- /include/gep_protocol.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP (Generic Event Protobuf aka Generic Eh... Protobuf) protocol 4 | 5 | #ifndef _GEP_PROTOCOL_H_ 6 | #define _GEP_PROTOCOL_H_ 7 | 8 | #include // for function 9 | #include // for map 10 | #include // for uint32_t, uint8_t 11 | #include // for string 12 | 13 | #include "gep_common.h" // for GepProtobufMessage 14 | 15 | // Callback function that will be called when a protobuf message is 16 | // received. 17 | // \param msg: the received message 18 | // \param context: (pointer to) a generic object 19 | // \return 0 if the callback went OK, >1 if not implemented, <0 otherwise 20 | typedef std::function GepCallback; 22 | 23 | typedef std::map GepVFT; 24 | 25 | // macro used to define tags 26 | constexpr int MakeTag(char a, char b, char c, char d) { 27 | return (((unsigned)(a) << 24) | ((b) << 16) | ((c) << 8) | (d)); 28 | } 29 | 30 | // Characters may need up to 4 characters to be printable ("\x00" for '\0'). 31 | // Add one for the final end of string. 32 | const int kMaxTagString = 4 * 4 + 1; 33 | 34 | class GepProtocol { 35 | public: 36 | explicit GepProtocol(int port); 37 | virtual ~GepProtocol(); 38 | 39 | // scans a buffer looking for a valid GEP header. Returns true if valid 40 | // header, false otherwise 41 | bool ScanHeader(uint8_t *buf, uint32_t *tag, uint32_t *value_len); 42 | // prints a a valid GEP header into a buffer 43 | void PrintHeader(uint32_t tag, uint32_t value_len, uint8_t *buf); 44 | 45 | // returns the tag associated to a message. 46 | virtual uint32_t GetTag(const GepProtobufMessage *msg) = 0; 47 | // constructs an object of a given type. 48 | virtual GepProtobufMessage *GetMessage(uint32_t tag) = 0; 49 | 50 | // converts a tag into a printable string 51 | int TagString(uint32_t tag, char *buf, int max_buf); 52 | 53 | // serializer code 54 | // Return an error code (true if ok, false if problems) 55 | bool Serialize(const GepProtobufMessage &msg, std::string *s); 56 | bool Unserialize(const std::string &s, GepProtobufMessage *msg); 57 | enum Mode { 58 | MODE_TEXT = 0, // use text-encoded protobuf messages 59 | MODE_BINARY = 1, // use binary-encoded protobuf messages 60 | }; 61 | void SetMode(Mode mode) { mode_ = mode; } 62 | Mode GetMode() const { return mode_; } 63 | 64 | // accessors 65 | int GetPort() const { return port_; } 66 | void SetPort(int port) { port_ = port; } 67 | static uint32_t GetHdrLen() { return kHdrLen; } 68 | static uint32_t GetOffsetValue() { return kOffsetValue; } 69 | int64_t GetSelectTimeoutUsec() { return select_timeout_usec_; } 70 | void SetSelectTimeoutUsec(int64_t select_timeout_usec); 71 | uint32_t GetMagic() const { return magic_; } 72 | void SetMagic(uint32_t magic) { magic_ = magic; } 73 | 74 | // GEP protocol constants 75 | // maximum length of a single message (including 12-byte header) 76 | static const uint32_t kMaxMsgLen = 1 << 20; 77 | 78 | protected: 79 | int port_; 80 | Mode mode_; 81 | #ifndef GEP_LITE 82 | // default in vanilla (non-lite) mode 83 | static constexpr Mode kMode = MODE_TEXT; 84 | #else 85 | // default (only option) in lite mode 86 | static constexpr Mode kMode = MODE_BINARY; 87 | #endif 88 | 89 | // GEP protocol header 90 | static const uint32_t kOffsetMagic = 0; 91 | static const uint32_t kOffsetTag = 4; 92 | static const uint32_t kOffsetLen = 8; 93 | static const uint32_t kOffsetValue = 12; 94 | static const uint32_t kHdrLen = kOffsetValue; 95 | 96 | // protocol header 97 | static constexpr uint32_t kMagic = MakeTag('g', 'e', 'p', 'p'); 98 | uint32_t magic_; 99 | 100 | // select() timeout, in usec 101 | int64_t select_timeout_usec_; 102 | }; 103 | 104 | #endif // _GEP_PROTOCOL_H_ 105 | -------------------------------------------------------------------------------- /include/gep_server.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP protocol: server side. 4 | 5 | #ifndef _GEP_SERVER_H_ 6 | #define _GEP_SERVER_H_ 7 | 8 | #include 9 | #include // for thread 10 | #include // for string 11 | #include // for NULL 12 | 13 | #include "gep_channel_array.h" 14 | #include "gep_common.h" // for GepProtobufMessage 15 | #include "gep_protocol.h" 16 | 17 | 18 | // internals 19 | class GepServer { 20 | public: 21 | // Creates a GEP server. The instance supports up to max_channels 22 | // GEP channels and (when started via Start()) kicks off a 23 | // service thread that listens on the provided TCP port for requests from 24 | // GEP clients (localhost only). Each connection creates a new GEP 25 | // channel and stays up for the entirety of an GEP session. 26 | // \param name: name used for logging 27 | // \param max_channels: maximum number of concurrent clients 28 | // \param context: a void* that will be passed to recv callbacks 29 | // \param proto: a GEP protocol object 30 | // \param ops: a GEP virtual function table 31 | GepServer(const std::string &name, int max_channels, void *context, 32 | GepProtocol *proto, const GepVFT* ops); 33 | virtual ~GepServer(); 34 | 35 | // Starts the given GEP server by opening the TCP service socket 36 | // and creating the service thread that accepts and manages GEP channel 37 | // connections. 38 | // Returns an error code (0 if ok, <0 if error). 39 | virtual int Start(); 40 | // Stops the given GEP server by terminating the server thread 41 | // and closing all open GEP channel connections. 42 | virtual void Stop(); 43 | 44 | // default function run by the server thread 45 | virtual void RunThread(); 46 | 47 | // accessors 48 | GepProtocol *GetProto() { return proto_; } 49 | GepChannelArray *GetGepChannelArray() { return gep_channel_array_; } 50 | int GetNumClients() { return gep_channel_array_->GetVectorSize(); } 51 | std::atomic &GetThreadCtrl() { return thread_ctrl_; } 52 | int GetPort() { return proto_->GetPort(); } 53 | 54 | // send API 55 | // Returns status value (0 if all ok, -1 for any error) 56 | virtual int Send(const GepProtobufMessage &msg); 57 | virtual int Send(const GepProtobufMessage &msg, int id); 58 | 59 | // client (dis)connection callbacks 60 | virtual void AddClient(int id) { } 61 | virtual void DelClient(int id) { } 62 | 63 | private: 64 | std::string name_; 65 | void *context_; // not owned 66 | GepProtocol *proto_; // owned and responsible for destruction 67 | const GepVFT* ops_; // not owned 68 | GepChannelArray *gep_channel_array_; // structure to manage channels (owned) 69 | std::thread thread_; 70 | std::atomic thread_ctrl_; 71 | }; 72 | 73 | #endif // _GEP_SERVER_H_ 74 | -------------------------------------------------------------------------------- /include/gep_utils.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP protocol: utils. 4 | 5 | #ifndef _GEP_UTILS_H_ 6 | #define _GEP_UTILS_H_ 7 | 8 | #include "gep_channel.h" // for GepChannel 9 | #include "gep_common.h" // for GepProtobufMessage 10 | 11 | // These callbacks, which can be fed to the VFT, gets the actual object from 12 | // the context variable, and then calls the corresponding protocol object 13 | // callback. 14 | template 15 | bool RecvMessageId(const GepProtobufMessage &msg, void *context) { 16 | const SpecificMessage &smsg = dynamic_cast(msg); 17 | // send specific msg to object callback 18 | GepChannel *gep_channel = static_cast(context); 19 | Class* that = static_cast(gep_channel->GetContext()); 20 | return that->Recv(smsg, gep_channel->GetId()); 21 | } 22 | 23 | template 24 | bool RecvMessage(const GepProtobufMessage &msg, void *context) { 25 | const SpecificMessage &smsg = dynamic_cast(msg); 26 | // send specific msg to object callback 27 | GepChannel *gep_channel = static_cast(context); 28 | Class* that = static_cast(gep_channel->GetContext()); 29 | return that->Recv(smsg); 30 | } 31 | 32 | #endif // _GEP_UTILS_H_ 33 | -------------------------------------------------------------------------------- /include/mock_gep_channel.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP Protocol: GEPChannel. 4 | 5 | #ifndef _MOCK_GEP_CHANNEL_H_ 6 | #define _MOCK_GEP_CHANNEL_H_ 7 | 8 | #include 9 | #include // for string 10 | 11 | #include "gep_channel.h" 12 | #include "gep_common.h" // for GepProtobufMessage 13 | #include "gep_protocol.h" // for GepProtocol (ptr only), etc 14 | 15 | // A mock version of GepChannel. 16 | class MockGepChannel : public GepChannel { 17 | public: 18 | explicit MockGepChannel(void *context) 19 | : GepChannel(0, "", NULL, NULL, context) { 20 | } 21 | ~MockGepChannel() override {} 22 | MOCK_METHOD1(SendMessage, int(const GepProtobufMessage &msg)); 23 | }; 24 | 25 | #endif // _MOCK_GEP_CHANNEL_H_ 26 | -------------------------------------------------------------------------------- /rules.mk: -------------------------------------------------------------------------------- 1 | # Copyright Google Inc. Apache 2.0. 2 | 3 | TOP:=$(shell /bin/pwd)/$(TOP) 4 | 5 | default: all 6 | 7 | NO_CLANG:= 8 | USE_TSAN:= 9 | USE_ASAN:= 10 | USE_MSAN:= 11 | USE_COV:= 12 | COV_OUT_DIR:= 13 | 14 | GENINFO:=geninfo 15 | GENHTML:=genhtml 16 | 17 | # symbolizer needed to decode MSAN/ASAN traces 18 | SYMBOLIZER_PATH=$(shell which llvm-symbolizer-3.4) 19 | 20 | # Test logs output directory 21 | TEST_OUT=$(TOP)/ 22 | # Sanitizer logs output directory 23 | TSAN_OUT=$(TOP)/tsan_logs 24 | ASAN_OUT=$(TOP)/asan_logs 25 | 26 | ifneq ($(NO_CLANG),) 27 | HOST_CC:=gcc 28 | HOST_CXX:=g++ 29 | else 30 | HOST_CC:=clang 31 | HOST_CXX:=clang++ 32 | endif 33 | 34 | ifeq ($(CROSS_PREFIX),) 35 | CC:=$(HOST_CC) 36 | CXX:=$(HOST_CXX) 37 | CPPFLAGS+=-I$(HOSTDIR)/usr/include 38 | LDFLAGS+=-L$(HOSTDIR)/usr/lib 39 | else 40 | CC:=$(CROSS_PREFIX)gcc 41 | CXX:=$(CROSS_PREFIX)g++ 42 | endif 43 | LD:=$(CROSS_PREFIX)ld 44 | AR:=$(CROSS_PREFIX)ar 45 | RANLIB:=$(CROSS_PREFIX)ranlib 46 | STRIP:=$(CROSS_PREFIX)strip 47 | INSTALL:=install 48 | 49 | # C-Preprocessor flags 50 | CPPFLAGS+=-D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 51 | CFLAGS=-Wall -Werror -g -fPIC -Wswitch-enum 52 | CXXFLAGS=-Wall -Werror -g -fPIC -Wswitch-enum -Wextra -Wno-sign-compare \ 53 | -Wno-unused-parameter -std=c++0x 54 | ifeq ($(NO_CLANG),) 55 | ifeq ($(CROSS_PREFIX),) 56 | CFLAGS+=-Wshadow 57 | CXXFLAGS+=-Wshadow 58 | endif 59 | endif 60 | LDFLAGS+=-rdynamic 61 | LIBS=-lpthread -lrt 62 | 63 | # add current project version 64 | LIBGEP_VERSION:= $(shell git describe --abbrev=4 --dirty --always --tags) 65 | CPPFLAGS+=-DLIBGEP_VERSION=\"$(LIBGEP_VERSION)\" 66 | 67 | # Thread Sanitizer 68 | ifneq ($(USE_TSAN),) 69 | CFLAGS+=-fsanitize=thread 70 | CXXFLAGS+=-fsanitize=thread 71 | LDFLAGS+=-fsanitize=thread 72 | endif 73 | 74 | # Address Sanitizer 75 | ifneq ($(USE_ASAN),) 76 | CFLAGS+=-fsanitize=address -fno-omit-frame-pointer 77 | CXXFLAGS+=-fsanitize=address -fno-omit-frame-pointer 78 | LDFLAGS+=-fsanitize=address 79 | endif 80 | 81 | # Memory Sanitizer 82 | ifneq ($(USE_MSAN),) 83 | CFLAGS+=-fsanitize=memory -fno-omit-frame-pointer 84 | CXXFLAGS+=-fsanitize=memory -fno-omit-frame-pointer 85 | LDFLAGS+=-fsanitize=memory 86 | endif 87 | 88 | # Code coverage 89 | ifneq ($(NO_OPTIMIZATION),) 90 | CFLAGS+=-O0 91 | CXXFLAGS+=-O0 92 | else ifeq ($(USE_COV),) 93 | # not running coverage, enable optimization 94 | CFLAGS+=-O3 95 | CXXFLAGS+=-O3 96 | else 97 | CFLAGS+=--coverage 98 | CXXFLAGS+=--coverage 99 | LDFLAGS+=--coverage 100 | ifneq ($(COV_OUT_DIR),) 101 | CFLAGS+=-fprofile-dir=$(COV_OUT_DIR) 102 | CXXFLAGS+=-fprofile-dir=$(COV_OUT_DIR) 103 | LDFLAGS+=-fprofile-dir=$(COV_OUT_DIR) 104 | endif 105 | ifeq ($(NO_CLANG),) 106 | CLANG_COV_FLAGS+=-Xclang -coverage-cfg-checksum -Xclang \ 107 | -coverage-no-function-names-in-data -Xclang -coverage-version='408*' \ 108 | --coverage 109 | CFLAGS+=$(CLANG_COV_FLAGS) 110 | CXXFLAGS+=$(CLANG_COV_FLAGS) 111 | endif 112 | endif 113 | 114 | # Test Flags 115 | TEST_CPPFLAGS=$(CPPFLAGS) 116 | TEST_CFLAGS=$(CFLAGS) 117 | TEST_CXXFLAGS=$(CXXFLAGS) 118 | ifneq ($(NO_CLANG),) 119 | TEST_CXXFLAGS+=-Wno-unused-local-typedefs 120 | endif 121 | TEST_LDFLAGS=$(LDFLAGS) 122 | TEST_LIBS=$(LIBS) -lgtest -pthread 123 | 124 | 125 | .PHONY: clean 126 | clean:: $(patsubst %,%/clean,$(SUBDIRS)) 127 | rm -f *.[oa] *~ .??*~ *.so *.gcno *.gcda *.gcov *.info 128 | rm -f $(TEST_TARGETS) $(TARGETS) 129 | 130 | 131 | %/all: 132 | $(MAKE) -C $* all 133 | %/test: 134 | $(MAKE) -C $* test 135 | %/test-full: 136 | $(MAKE) -C $* test-full 137 | %/test-lite: 138 | $(MAKE) -C $* test-lite 139 | %/tests: 140 | $(MAKE) -C $* tests 141 | %/clean: 142 | $(MAKE) -C $* clean 143 | %/cov: 144 | $(MAKE) -C $* cov 145 | %/test-tsan: 146 | $(MAKE) -C $* test-tsan 147 | %/test-asan: 148 | $(MAKE) -C $* test-asan 149 | 150 | 151 | .PHONY: subdirs 152 | subdirs: $(patsubst %,%/all,$(SUBDIRS)) 153 | 154 | 155 | # You can run 'make tests' to build all the test programs without running 156 | # them. Usually you want to check compilation before you check 157 | # functionality (since some tests take a while to run) so this saves 158 | # time. 159 | tests: all 160 | $(MAKE) build_tests 161 | 162 | build_tests: \ 163 | $(patsubst %,%/tests,$(SUBDIRS)) \ 164 | $(TEST_TARGETS) 165 | 166 | # Almost like "test: tests runtests", but this method forces tests to 167 | # always completely finish before runtests begins. 168 | test: tests 169 | $(MAKE) -k runtests 170 | 171 | runtests: \ 172 | $(patsubst %,%/test,$(SUBDIRS)) \ 173 | $(patsubst %,%.test,$(TEST_TARGETS)) 174 | 175 | # add full vs. lite tests (which reuse the same source code) 176 | test-full: tests 177 | $(MAKE) -k runtests-full 178 | 179 | runtests-full: \ 180 | $(patsubst %,%/test,$(SUBDIRS)) \ 181 | $(patsubst %,%.test,$(TEST_TARGETS_FULL)) 182 | 183 | test-lite: tests 184 | $(MAKE) -k runtests-lite 185 | 186 | runtests-lite: \ 187 | $(patsubst %,%/test,$(SUBDIRS)) \ 188 | $(patsubst %,%.test,$(TEST_TARGETS_LITE)) 189 | 190 | 191 | cov: $(patsubst %,%/cov,$(COV_SUBDIRS)) 192 | 193 | tests-tsan: 194 | $(MAKE) USE_TSAN=1 tests 195 | 196 | test-tsan: tests-tsan 197 | $(MAKE) -k USE_TSAN=1 runtests-tsan 198 | 199 | runtests-tsan: \ 200 | $(patsubst %,%/test-tsan,$(SUBDIRS)) \ 201 | $(patsubst %,%.tsan,$(filter-out $(SKIP_TSAN_TARGETS),$(TEST_TARGETS))) 202 | 203 | tests-asan: 204 | $(MAKE) USE_ASAN=1 tests 205 | 206 | test-asan: tests-asan 207 | $(MAKE) -k USE_ASAN=1 runtests-asan 208 | 209 | runtests-asan: \ 210 | $(patsubst %,%/test-asan,$(SUBDIRS)) \ 211 | $(patsubst %,%.asan,$(filter-out $(SKIP_ASAN_TARGETS),$(TEST_TARGETS))) 212 | 213 | msan: 214 | $(MAKE) USE_MSAN=1 tests 215 | MSAN_SYMBOLIZER_PATH=$(SYMBOLIZER_PATH) $(MAKE) -j1 USE_MSAN=1 test 216 | 217 | # TODO(apenwarr): could use real autodepends here. 218 | # This declares a dependency of every object on every header, which is 219 | # a bit heavyweight, but simple and safe. 220 | $(patsubst %.cc,%.o,$(wildcard *.cc)) \ 221 | $(patsubst %.c,%.o,$(wildcard *.c)): $(shell find $(TOP) -name '*.h') 222 | 223 | 224 | # Pattern rules for test targets. 225 | # TODO(apenwarr): eliminate this. Tests shouldn't need special CFLAGS. 226 | # Currently some of the object files act differently when compiled with 227 | # different flags, which is bad practice; you want to test the actual 228 | # code that will run on the target platform. But let's keep it this 229 | # way for now in order to not mix Makefile changes with code changes. 230 | # (In particular, some of the files only define main() if a particular 231 | # flag is set. It would be better to split main() into its own file.) 232 | %.t.o: %.cc 233 | $(CXX) $(TEST_CPPFLAGS) $(TEST_CXXFLAGS) -c -o $@ $< 234 | %.t.o: %.c 235 | $(CC) $(TEST_CPPFLAGS) $(TEST_CFLAGS) -c -o $@ $< 236 | %_test: %_test.t.o 237 | $(CXX) $(TEST_LDFLAGS) -o $@ -Wl,--start-group \ 238 | $(filter-out $*.o,$^) -Wl,--end-group $(TEST_LIBS) 239 | %: %.t.o 240 | $(CXX) $(TEST_LDFLAGS) -o $@ $(filter-out $*.o,$^) $(LIBS) 241 | 242 | 243 | # Pattern rules for normal targets 244 | %.o: %.cc 245 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< 246 | %.o: %.c 247 | $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< 248 | %: %.o 249 | $(CXX) $(LDFLAGS) -o $@ $^ $(LIBS) 250 | 251 | # Rule to run tests. It tees the output into a .log file. If the test runs 252 | # successfully the log file is removed. 253 | %.test: % 254 | GTEST_COLOR=1 stdbuf -o0 ./$* 2>&1 | tee $(TEST_OUT)/$*.log; \ 255 | if [ $${PIPESTATUS[0]} -eq 0 ]; then rm $(TEST_OUT)/$*.log; else exit 1; fi 256 | 257 | # Rule to run tests with TSAN. It runs the test and feeds the test output 258 | # (stdout) into .log and the ThreadSanitizer output (stderr) into both .log and 259 | # .tsan.log. If the test ran successfully, i.e. no errors were found in either 260 | # the test itself or by ThreadSanitizer, it deletes these files. 261 | %.tsan: % 262 | (GTEST_COLOR=1 stdbuf -o0 ./$* 3>&1 1>&2 2>&3 | tee $(TSAN_OUT)/$*.tsan.log; \ 263 | if [ $${PIPESTATUS[0]} -ne 0 ]; then exit 1; fi) > $(TSAN_OUT)/$*.log 2>&1 \ 264 | && rm $(TSAN_OUT)/$*.log $(TSAN_OUT)/$*.tsan.log 265 | 266 | # Rule to run tests with ASAN. It runs the test and feeds the test output 267 | # (stdout) into .log and the AddressSanitizer output (stderr) into both .log 268 | # and .asan.log. If the test ran successfully, i.e. no errors were found in 269 | # either the test itself or by AddressSanitizer, it deletes these files. 270 | %.asan: % 271 | (GTEST_COLOR=1 ASAN_SYMBOLIZER_PATH=$(SYMBOLIZER_PATH) stdbuf -o0 ./$* \ 272 | 3>&1 1>&2 2>&3 | tee $(ASAN_OUT)/$*.asan.log; \ 273 | if [ $${PIPESTATUS[0]} -ne 0 ]; then exit 1; fi) > $(ASAN_OUT)/$*.log 2>&1 \ 274 | && rm $(ASAN_OUT)/$*.log $(ASAN_OUT)/$*.asan.log 275 | 276 | define make_lib 277 | $(AR) q $@.new $(filter %.o,$^) 278 | mv $@.new $@ 279 | endef 280 | 281 | # protobuf defines 282 | ifneq ($(PROTOBUF_PREFIX),) 283 | HOST_PROTOC ?= $(PROTOBUF_PREFIX)/bin/protoc 284 | else 285 | HOSTDIR_MAIN = $(HOSTDIR)/usr 286 | HOST_PROTOC ?= $(HOSTDIR_MAIN)/bin/protoc 287 | endif 288 | 289 | 290 | PROTOC_FLAGS=--cpp_out=. -I. 291 | 292 | PROTO_CPPFLAGS=-I. -I$(PROTOBUF_PREFIX)/include 293 | PROTOFULL_LDFLAGS=-L$(PROTOBUF_PREFIX)/lib -lprotobuf 294 | PROTOLITE_LDFLAGS=-L$(PROTOBUF_PREFIX)/lib -lprotobuf-lite 295 | 296 | -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Google Inc. Apache 2.0. 2 | 3 | TOP:=.. 4 | TARGETS=libgepserver.a libgepclient.a libgepserver-lite.a libgepclient-lite.a 5 | CPPFLAGS+=-I. -I.. -I../include $(PROTO_CPPFLAGS) 6 | LIBS+=$(PROTOFULL_LDFLAGS) 7 | 8 | include ../rules.mk 9 | 10 | all: $(TARGETS) 11 | 12 | libgepserver.a: \ 13 | socket_interface.o \ 14 | time_manager.o \ 15 | utils.o \ 16 | gep_protocol.o \ 17 | gep_channel.o \ 18 | gep_channel_array.o \ 19 | gep_server.o 20 | $(make_lib) 21 | 22 | libgepclient.a: \ 23 | socket_interface.o \ 24 | time_manager.o \ 25 | utils.o \ 26 | gep_protocol.o \ 27 | gep_channel.o \ 28 | gep_channel_array.o \ 29 | gep_client.o 30 | $(make_lib) 31 | 32 | utils_lite.o: utils.cc 33 | $(CXX) $(TEST_CPPFLAGS) $(TEST_CXXFLAGS) -DGEP_LITE -c -o $@ $< 34 | 35 | gep_protocol_lite.o: gep_protocol.cc 36 | $(CXX) $(TEST_CPPFLAGS) $(TEST_CXXFLAGS) -DGEP_LITE -c -o $@ $< 37 | 38 | gep_channel_lite.o: gep_channel.cc 39 | $(CXX) $(TEST_CPPFLAGS) $(TEST_CXXFLAGS) -DGEP_LITE -c -o $@ $< 40 | 41 | gep_channel_array_lite.o: gep_channel_array.cc 42 | $(CXX) $(TEST_CPPFLAGS) $(TEST_CXXFLAGS) -DGEP_LITE -c -o $@ $< 43 | 44 | gep_server_lite.o: gep_server.cc 45 | $(CXX) $(TEST_CPPFLAGS) $(TEST_CXXFLAGS) -DGEP_LITE -c -o $@ $< 46 | 47 | gep_client_lite.o: gep_client.cc 48 | $(CXX) $(TEST_CPPFLAGS) $(TEST_CXXFLAGS) -DGEP_LITE -c -o $@ $< 49 | 50 | libgepserver-lite.a: CPPFLAGS+=-DGEP_LITE 51 | 52 | libgepserver-lite.a: \ 53 | socket_interface.o \ 54 | time_manager.o \ 55 | utils_lite.o \ 56 | gep_protocol_lite.o \ 57 | gep_channel_lite.o \ 58 | gep_channel_array_lite.o \ 59 | gep_server_lite.o 60 | $(make_lib) 61 | 62 | libgepclient-lite.a: CPPFLAGS+=-DGEP_LITE 63 | 64 | libgepclient-lite.a: \ 65 | socket_interface.o \ 66 | time_manager.o \ 67 | utils_lite.o \ 68 | gep_protocol_lite.o \ 69 | gep_channel_lite.o \ 70 | gep_channel_array_lite.o \ 71 | gep_client_lite.o 72 | $(make_lib) 73 | 74 | install: 75 | $(INSTALL) -D -m 0755 \ 76 | libgepserver.a \ 77 | libgepclient.a \ 78 | libgepserver-lite.a \ 79 | libgepclient-lite.a \ 80 | $(DESTDIR)/usr/lib/ 81 | 82 | clean:: 83 | rm -f *.o libgepserver.a libgepclient.a libgepserver-lite.a libgepclient-lite.a 84 | -------------------------------------------------------------------------------- /src/gep_channel.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP Protocol: GEPChannel implementation. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "gep_channel.h" 15 | 16 | #include // for errno, ECONNRESET 17 | #include 18 | #include // for _Rb_tree_const_iterator 19 | #include 20 | #include // for sockaddr_in, htonl, htons, etc 21 | #include // for memmove 22 | #include // for AF_INET, connect, recv, etc 23 | #include // for close, usleep 24 | #include // for pair 25 | 26 | #include "gep_common.h" // for GepProtobufMessage 27 | #include "socket_interface.h" // for SocketInterface 28 | #include "utils.h" // for snprintf_printable 29 | 30 | using namespace libgep_utils; 31 | 32 | GepChannel::GepChannel(int id, const std::string &name, 33 | GepProtocol *proto, const GepVFT *ops, 34 | void *context, int socket) 35 | : name_(name), 36 | proto_(proto), 37 | ops_(ops), 38 | context_(context), 39 | id_(id), 40 | socket_(socket), 41 | len_(0) { 42 | socket_interface_ = new SocketInterface(); 43 | } 44 | 45 | GepChannel::~GepChannel() { 46 | Close(); 47 | delete socket_interface_; 48 | } 49 | 50 | 51 | int GepChannel::OpenClientSocket() { 52 | int port = proto_->GetPort(); 53 | 54 | // Open socket. 55 | int new_socket; 56 | if ((new_socket = socket_interface_->Socket(AF_INET, SOCK_STREAM, 0)) == -1) { 57 | gep_log(LOG_ERROR, 58 | "%s(%i):Error-cannot open client socket", 59 | name_.c_str(), id_); 60 | return -1; 61 | } 62 | 63 | // Connect socket. 64 | struct sockaddr_in saddr; 65 | saddr.sin_family = AF_INET; 66 | saddr.sin_port = htons(port); 67 | // Use INADDR_LOOPBACK as server address to connect to the localhost. 68 | // You have to bind the server to the same INADDR_LOOPBACK address for 69 | // the latter to only accept calls from there. 70 | saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 71 | if (connect(new_socket, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) { 72 | gep_log(LOG_ERROR, 73 | "%s(%i):Error-cannot connect client socket %i", 74 | name_.c_str(), id_, new_socket); 75 | close(new_socket); 76 | return -1; 77 | } 78 | std::lock_guard lock_guard(socket_lock_); 79 | socket_ = new_socket; 80 | gep_log(LOG_DEBUG, 81 | "%s(%i):open client socket %d", 82 | name_.c_str(), id_, socket_); 83 | return 0; 84 | } 85 | 86 | int GepChannel::Close() { 87 | if (socket_ != -1) { 88 | std::lock_guard lock_guard(socket_lock_); 89 | gep_log(LOG_DEBUG, 90 | "%s(%i):closed socket %d", 91 | name_.c_str(), id_, socket_); 92 | close(socket_); 93 | socket_ = -1; 94 | len_ = 0; 95 | return 0; 96 | } 97 | return -1; 98 | } 99 | 100 | int GepChannel::GetSocket() { 101 | std::lock_guard lock_guard(socket_lock_); 102 | return socket_; 103 | } 104 | 105 | void GepChannel::SetSocket(int socket) { 106 | std::lock_guard lock_guard(socket_lock_); 107 | socket_ = socket; 108 | } 109 | 110 | bool GepChannel::IsOpenSocket() { 111 | std::lock_guard lock_guard(socket_lock_); 112 | return socket_ != -1; 113 | } 114 | 115 | int GepChannel::RecvData() { 116 | if (socket_ < 0) { 117 | gep_log(LOG_ERROR, 118 | "%s:recv(%i):Error-invalid socket %d", 119 | name_.c_str(), id_, socket_); 120 | return -1; 121 | } 122 | 123 | // check there is some space in the buffer 124 | if (len_ >= sizeof(buf_)) { 125 | gep_log(LOG_ERROR, 126 | "%s:recv(%i):Error-buf_ full (%i/%zu)", 127 | name_.c_str(), id_, len_, sizeof(buf_)); 128 | return -1; 129 | } 130 | 131 | // read new data from command socket and append to any leftover one 132 | socket_lock_.lock(); 133 | int bytes = socket_interface_->Recv(socket_, buf_ + len_, 134 | sizeof(buf_) - len_, 0); 135 | socket_lock_.unlock(); 136 | 137 | // TODO(chema): support EAGAIN and EWOULDBLOCK 138 | if (bytes > 0) { 139 | len_ += bytes; 140 | if (RecvString() == CMD_ERROR) { 141 | gep_log(LOG_ERROR, 142 | "%s:recv(%i):Error-Incorrect data received on socket %d", 143 | name_.c_str(), id_, socket_); 144 | return -1; 145 | } 146 | } else if (bytes == 0) { 147 | gep_log(LOG_DEBUG, 148 | "%s:recv(%i):socket %d was closed by peer", 149 | name_.c_str(), id_, socket_); 150 | return -2; 151 | } else { 152 | gep_perror(errno, "%s:recv(%i):Error-recv() failed on socket %d:", 153 | name_.c_str(), id_, socket_); 154 | return -1; 155 | } 156 | return 0; 157 | } 158 | 159 | int GepChannel::SendData(const char *buf, int bytes) { 160 | int sent = socket_interface_->FullSend(socket_, (const uint8_t *)buf, bytes, 161 | kGepSendTimeoutMs); 162 | if (sent == 0) { 163 | gep_log(LOG_DEBUG, 164 | "%s:send(%i):socket %d was closed by peer", 165 | name_.c_str(), id_, socket_); 166 | } else if (sent == -2) { 167 | gep_log(LOG_DEBUG, 168 | "%s:send(%i):socket %d was closed by peer", 169 | name_.c_str(), id_, socket_); 170 | } else if (sent == -1) { 171 | gep_perror(errno, "%s:send(%d):Error-failed sending %d bytes on " 172 | "socket %d", name_.c_str(), id_, bytes, socket_); 173 | } 174 | 175 | // return the number of bytes sent 176 | return sent; 177 | } 178 | 179 | GepChannel::Result GepChannel::RecvString() { 180 | while (len_ >= proto_->GetHdrLen()) { 181 | uint32_t tag; 182 | uint32_t value_len; 183 | if (!proto_->ScanHeader(buf_, &tag, &value_len)) { 184 | char tmp[4 * 4 + 1]; 185 | snprintf_printable(tmp, sizeof(tmp), buf_, 4); 186 | gep_log(LOG_ERROR, 187 | "%s:recv(*):Error-Wrong magic number (%s)", 188 | name_.c_str(), tmp); 189 | len_ = 0; 190 | return CMD_ERROR; 191 | } 192 | 193 | // ensure the value length is ok for GEP 194 | if (value_len >= (GepProtocol::kMaxMsgLen - proto_->GetHdrLen())) { 195 | gep_log(LOG_ERROR, 196 | "%s:recv(%i):Error-Value length too large (%" PRIu32 197 | " >= %" PRIu32 ")", 198 | name_.c_str(), id_, value_len, GepProtocol::kMaxMsgLen); 199 | len_ = 0; 200 | return CMD_ERROR; 201 | } 202 | uint32_t msg_len = proto_->GetHdrLen() + value_len; 203 | 204 | // process fragmented packets 205 | if (len_ < msg_len) { 206 | // value is not complete in command buffer, wait for more 207 | gep_log(LOG_DEBUG, 208 | "%s:recv(%i):Command is fragmented (recv %i bytes)", 209 | name_.c_str(), id_, len_); 210 | return CMD_FRAGMENTED; 211 | } 212 | 213 | // receive the packet 214 | uint8_t *value = buf_ + proto_->GetOffsetValue(); 215 | if (gep_log_get_level() >= LOG_DEBUG) { 216 | char tmp[value_len * 4 + 1]; 217 | snprintf_printable(tmp, sizeof(tmp), value, value_len); 218 | char tag_string[kMaxTagString]; 219 | proto_->TagString(tag, tag_string, kMaxTagString); 220 | gep_log(LOG_DEBUG, 221 | "%s:recv(%i):Received command (%i bytes) {%s, %u, %s}", 222 | name_.c_str(), id_, msg_len, 223 | tag_string, value_len, tmp); 224 | } 225 | 226 | // unpack and recv the message 227 | Result ret = RecvTLV(tag, value_len, value); 228 | if (!IsRecoverable(ret)) 229 | return ret; 230 | 231 | // process remaining data 232 | int remain = len_ - msg_len; 233 | if (remain) { 234 | // copy left-over to the beginning of the buffer 235 | memmove(buf_, buf_ + msg_len, remain); 236 | gep_log(LOG_DEBUG, 237 | "%s:recv(%i):Fragmented command (left %d bytes)", 238 | name_.c_str(), id_, len_); 239 | } 240 | len_ = remain; 241 | } 242 | return len_ ? CMD_FRAGMENTED : CMD_OK; 243 | } 244 | 245 | int GepChannel::SendString(uint32_t tag, const std::string &s) { 246 | // send the TLV 247 | const char *value = s.c_str(); 248 | int value_len = s.length(); 249 | // return the total number of bytes sent 250 | return SendTLV(tag, value_len, value); 251 | } 252 | 253 | GepChannel::Result GepChannel::RecvTLV(uint32_t tag, int value_len, 254 | const uint8_t *value) { 255 | char tag_string[kMaxTagString]; 256 | proto_->TagString(tag, tag_string, kMaxTagString); 257 | auto iter = ops_->find(tag); 258 | if (iter != ops_->end()) { 259 | GepProtobufMessage *msg = proto_->GetMessage(tag); 260 | gep_log(LOG_DEBUG, 261 | "%s:recv(%i):Received message with tag [%s] (%d value bytes)", 262 | name_.c_str(), id_, tag_string, value_len); 263 | std::string value_str((const char *)value, (size_t)value_len); 264 | if (!proto_->Unserialize(value_str, msg)) { 265 | char tmp[value_len * 4]; 266 | snprintf_printable(tmp, value_len * 4, value, value_len); 267 | gep_log(LOG_WARNING, 268 | "%s:recv(%i):Error-Unpackable message with tag [%s] (%d bytes) " 269 | "[%s]", 270 | name_.c_str(), id_, tag_string, len_, tmp); 271 | delete msg; 272 | return CMD_ERROR; 273 | } 274 | bool ret = iter->second(*msg, this); 275 | if (!ret) { 276 | gep_log(LOG_WARNING, 277 | "%s:recv(%i):callback error [%s]", 278 | name_.c_str(), id_, tag_string); 279 | } 280 | delete msg; 281 | } else { 282 | gep_log(LOG_WARNING, 283 | "%s:recv(%i):Error-Unsupported tag [%s] (%d bytes)", 284 | name_.c_str(), id_, tag_string, len_); 285 | return CMD_DROPPED; 286 | } 287 | 288 | return CMD_OK; 289 | } 290 | 291 | // Send a message with the given message tag and optional value[value_len] 292 | // to the GEP client. 293 | // Returns number of bytes sent, -1 for error. 294 | int GepChannel::SendTLV(uint32_t tag, int value_len, const char *value) { 295 | // mutex is held for all this function to ensure header and data are 296 | // sent consecutively 297 | std::lock_guard lock_guard(socket_lock_); 298 | 299 | if (!value) value_len = 0; 300 | char tag_string[kMaxTagString]; 301 | proto_->TagString(tag, tag_string, kMaxTagString); 302 | 303 | // send protocol header 304 | int hdr_len = proto_->GetHdrLen(); 305 | char buf[hdr_len]; 306 | proto_->PrintHeader(tag, value_len, reinterpret_cast(buf)); 307 | int ret1 = SendData(buf, hdr_len); 308 | if (ret1 != hdr_len) { 309 | gep_log(LOG_ERROR, 310 | "%s:send(%i):Error-Only sent %d/%d hdr bytes to host", 311 | name_.c_str(), id_, ret1, hdr_len); 312 | return -1; 313 | } 314 | gep_log(LOG_DEBUG, 315 | "%s:send(%i):sent header:%s, %d/%d bytes", 316 | name_.c_str(), id_, tag_string, ret1, hdr_len + value_len); 317 | 318 | // check if there is a value to send 319 | if (value_len <= 0) 320 | return 0; 321 | 322 | // send value 323 | int ret2 = SendData(value, value_len); 324 | if (ret2 != value_len) { 325 | gep_log(LOG_ERROR, 326 | "%s:send(%i):Error-Only sent %d/%d data bytes to host", 327 | name_.c_str(), id_, ret2, value_len); 328 | return -1; 329 | } 330 | gep_log(LOG_DEBUG, 331 | "%s:send(%i):sent message:%s, %d/%d bytes", 332 | name_.c_str(), id_, tag_string, ret2, 333 | hdr_len + value_len); 334 | // return error code 335 | return 0; 336 | } 337 | 338 | int GepChannel::SendMessage(const GepProtobufMessage &msg) { 339 | // serialize the message 340 | std::string s; 341 | if (!proto_->Serialize(msg, &s)) { 342 | gep_log(LOG_ERROR, 343 | "%s:send(%i):Error-%s:serializing message", 344 | name_.c_str(), id_, __func__); 345 | return -1; 346 | } 347 | // send the string 348 | return SendString(proto_->GetTag(&msg), s); 349 | } 350 | -------------------------------------------------------------------------------- /src/gep_channel_array.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP protocol: server's array of GepChannel. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "gep_channel_array.h" 15 | 16 | #include // for max 17 | #include // for errno 18 | #include 19 | #include // for sockaddr_in, htons, etc 20 | #include // for memset 21 | #include // for AF_INET, accept, bind, etc 22 | #include // for close 23 | 24 | #include "gep_channel.h" // for GepChannel 25 | #include "gep_common.h" // for GepProtobufMessage 26 | #include "gep_server.h" // for GepChannel 27 | #include "socket_interface.h" // for SocketInterface 28 | #include "utils.h" // for gep_log, gep_perror, etc 29 | 30 | using namespace libgep_utils; 31 | 32 | GepChannelArray::GepChannelArray(const std::string &name, GepServer *server, 33 | GepProtocol *proto, int max_channels, 34 | const GepVFT *ops, void *context) 35 | : name_(name), 36 | server_(server), 37 | proto_(proto), 38 | ops_(ops), 39 | context_(context), 40 | max_channels_(max_channels), 41 | last_channel_id_(0), 42 | server_socket_(-1) { 43 | socket_interface_ = new SocketInterface(); 44 | } 45 | 46 | GepChannelArray::~GepChannelArray() { 47 | delete socket_interface_; 48 | } 49 | 50 | void GepChannelArray::ClearGepChannelVector() { 51 | Stop(); 52 | } 53 | 54 | int GepChannelArray::OpenServerSocket() { 55 | int sock_fd; 56 | 57 | if ((sock_fd = socket_interface_->Socket(AF_INET, SOCK_STREAM, 0)) == -1) { 58 | gep_perror(errno, "%s(*):Error-opening socket failed-", name_.c_str()); 59 | return -1; 60 | } 61 | 62 | if (socket_interface_->SetReuseAddr(name_.c_str(), sock_fd) < 0) { 63 | close(sock_fd); 64 | return -1; 65 | } 66 | 67 | socket_interface_->SetNonBlocking(name_.c_str(), sock_fd); 68 | socket_interface_->SetNoDelay(name_.c_str(), sock_fd); 69 | socket_interface_->SetPriority(name_.c_str(), sock_fd, 4); 70 | 71 | struct sockaddr_in serveraddr; 72 | serveraddr.sin_family = AF_INET; 73 | // restrict to local address 74 | serveraddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 75 | serveraddr.sin_port = htons(proto_->GetPort()); 76 | memset(&(serveraddr.sin_zero), '\0', 8); 77 | 78 | if (socket_interface_->Bind(sock_fd, (struct sockaddr*)&serveraddr, 79 | sizeof(serveraddr)) == -1) { 80 | gep_perror(errno, "%s(*):Error-bind service socket-", name_.c_str()); 81 | close(sock_fd); 82 | return -1; 83 | } 84 | 85 | if (socket_interface_->Listen(sock_fd, 4) == -1) { 86 | gep_perror(errno, "%s(*):Error-listen on service socket-", 87 | name_.c_str()); 88 | close(sock_fd); 89 | return -1; 90 | } 91 | 92 | if (proto_->GetPort() == 0) { 93 | // port was dynamically assigned, get it and save it 94 | int port; 95 | if (socket_interface_->GetPort(name_.c_str(), sock_fd, &port) < 0) { 96 | close(sock_fd); 97 | return -1; 98 | } 99 | proto_->SetPort(port); 100 | } 101 | 102 | server_socket_ = sock_fd; 103 | gep_log(LOG_DEBUG, 104 | "%s(*):open control socket %d on port %d.", 105 | name_.c_str(), sock_fd, proto_->GetPort()); 106 | 107 | return 0; 108 | } 109 | 110 | int GepChannelArray::Stop() { 111 | std::lock_guard lock(gep_channel_vector_lock_); 112 | if (server_socket_ >= 0) { 113 | gep_log(LOG_DEBUG, 114 | "%s(*):GepChannelArray::Stop(), closing service socket %d", 115 | name_.c_str(), server_socket_); 116 | close(server_socket_); 117 | server_socket_ = -1; 118 | } 119 | 120 | // Delete all GepChannel's 121 | for (auto &gep_channel_ptr : gep_channel_vector_) { 122 | server_->DelClient(gep_channel_ptr->GetId()); 123 | } 124 | gep_channel_vector_.clear(); 125 | 126 | return 0; 127 | } 128 | 129 | int GepChannelArray::AddChannel(int socket) { 130 | std::lock_guard lock(gep_channel_vector_lock_); 131 | if (gep_channel_vector_.size() >= max_channels_) { 132 | gep_log(LOG_ERROR, 133 | "%s(*):Error-Too many clients", name_.c_str()); 134 | return -1; 135 | } 136 | int id = last_channel_id_++; 137 | gep_channel_vector_.push_back(std::shared_ptr( 138 | new GepChannel(id, "gep_channel", proto_, ops_, context_, socket))); 139 | gep_log(LOG_DEBUG, 140 | "%s(%d):add GEP channel using socket %d", 141 | name_.c_str(), id, socket); 142 | server_->AddClient(id); 143 | return 0; 144 | } 145 | 146 | int GepChannelArray::AcceptConnection() { 147 | int new_socket; 148 | struct sockaddr clientaddr; 149 | socklen_t addrlen = sizeof(struct sockaddr_in); 150 | if ((new_socket = socket_interface_->Accept(server_socket_, &clientaddr, 151 | &addrlen)) == -1) { 152 | gep_perror(errno, "%s(*):ERROR accepting new connection using " 153 | "socket %d", name_.c_str(), new_socket); 154 | return -1; 155 | } 156 | char tmp[32]; 157 | char *peer_ip = socket_interface_->GetPeerIP(new_socket, tmp, sizeof(tmp)); 158 | gep_log(LOG_DEBUG, 159 | "%s(*):socket %d accepted connection from %s using socket %d", 160 | name_.c_str(), server_socket_, peer_ip, new_socket); 161 | socket_interface_->SetNonBlocking(name_.c_str(), new_socket); 162 | socket_interface_->SetNoDelay(name_.c_str(), new_socket); 163 | socket_interface_->SetPriority(name_.c_str(), new_socket, 4); 164 | AddChannel(new_socket); 165 | return 0; 166 | } 167 | 168 | // Returns -1 if any of the channels fails, 0 otherwise 169 | int GepChannelArray::SendMessage(const GepProtobufMessage &msg) { 170 | std::lock_guard lock(gep_channel_vector_lock_); 171 | // send the message to all open GepChannel's 172 | int ret = 0; 173 | for (auto &gep_channel_ptr : gep_channel_vector_) { 174 | if (gep_channel_ptr->IsOpenSocket()) 175 | if (gep_channel_ptr->SendMessage(msg) < 0) 176 | ret = -1; 177 | } 178 | return ret; 179 | } 180 | 181 | // Returns -1 if the message couldn't be sent, 0 otherwise 182 | int GepChannelArray::SendMessage(const GepProtobufMessage &msg, int id) { 183 | std::lock_guard lock(gep_channel_vector_lock_); 184 | // send the message to a specific GepChannel's 185 | for (auto &gep_channel_ptr : gep_channel_vector_) { 186 | if (gep_channel_ptr->IsOpenSocket() && id == gep_channel_ptr->GetId()) 187 | return gep_channel_ptr->SendMessage(msg); 188 | } 189 | return -1; 190 | } 191 | 192 | int GepChannelArray::GetVectorSize() { 193 | std::lock_guard lock(gep_channel_vector_lock_); 194 | return gep_channel_vector_.size(); 195 | } 196 | 197 | int GepChannelArray::GetVectorSocket(int i) { 198 | std::lock_guard lock(gep_channel_vector_lock_); 199 | return gep_channel_vector_[i]->GetSocket(); 200 | } 201 | 202 | int GepChannelArray::GetClientId(int i) { 203 | std::lock_guard lock(gep_channel_vector_lock_); 204 | return gep_channel_vector_[i]->GetId(); 205 | } 206 | 207 | void GepChannelArray::GetVectorReadFds(int *max_fds, fd_set *read_fds) { 208 | std::lock_guard lock(gep_channel_vector_lock_); 209 | for (const auto &gep_channel_ptr : gep_channel_vector_) { 210 | int socket = gep_channel_ptr->GetSocket(); 211 | if (socket < 0) { 212 | gep_log(LOG_ERROR, 213 | "%s(*):Error-invalid client socket (%i)", 214 | name_.c_str(), gep_channel_ptr->GetId()); 215 | continue; 216 | } 217 | FD_SET(socket, read_fds); 218 | *max_fds = std::max(socket, *max_fds); 219 | } 220 | } 221 | 222 | void GepChannelArray::RecvData(fd_set *read_fds) { 223 | // select any open channel 224 | std::shared_ptr used_gep_channel_ptr = nullptr; 225 | { 226 | std::lock_guard lock(gep_channel_vector_lock_); 227 | for (auto &gep_channel_ptr : gep_channel_vector_) { 228 | int socket = gep_channel_ptr->GetSocket(); 229 | if (socket >= 0 && FD_ISSET(socket, read_fds)) { 230 | used_gep_channel_ptr = gep_channel_ptr; 231 | break; 232 | } 233 | } 234 | } 235 | 236 | if (used_gep_channel_ptr == nullptr) 237 | return; 238 | 239 | // on the open channel: 240 | // * handle incoming request from GEP clients 241 | int ret = used_gep_channel_ptr->RecvData(); 242 | 243 | // * check for timeout 244 | if (ret < 0) { 245 | std::lock_guard lock(gep_channel_vector_lock_); 246 | // ensure the gep_channel still exists before deleting it 247 | for (auto it = gep_channel_vector_.begin(); 248 | it != gep_channel_vector_.end(); ) { 249 | std::shared_ptr gep_channel_ptr = *it; 250 | if (used_gep_channel_ptr == gep_channel_ptr) { 251 | server_->DelClient((*it)->GetId()); 252 | it = gep_channel_vector_.erase(it); 253 | break; 254 | } 255 | ++it; 256 | } 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /src/gep_client.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP protocol: client implementation. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "gep_client.h" 15 | 16 | #include // for errno, EINTR 17 | #include // for int64_t, uint32_t 18 | #include // for NULL 19 | #include // for FD_ISSET, FD_SET, select, etc 20 | #include // for timeval 21 | #include // for __NR_gettid 22 | #include // NOLINT 23 | #include // for syscall, pid_t 24 | 25 | #include "gep_channel.h" // for GepChannel 26 | #include "gep_common.h" // for GepProtobufMessage 27 | #include "gep_protocol.h" // for GepProtocol, etc 28 | #include "utils.h" // for LOG_ERROR, gep_log, etc 29 | 30 | using namespace libgep_utils; 31 | 32 | static const uint32_t kReconnectRetryDelaySecs = 5; 33 | 34 | GepClient::GepClient(const std::string &name, void *context, 35 | GepProtocol *proto, const GepVFT* ops) 36 | : name_(name), 37 | context_(context), 38 | proto_(proto), 39 | ops_(ops), 40 | thread_ctrl_(false), 41 | reconnect_count_(0) { 42 | gep_channel_ = new GepChannel(0, name_, proto_, ops_, context_); 43 | } 44 | 45 | GepClient::~GepClient() { 46 | // close and free the GEP channel 47 | delete gep_channel_; 48 | delete proto_; 49 | } 50 | 51 | int GepClient::Start() { 52 | if (gep_channel_->OpenClientSocket() < 0) { 53 | gep_log(LOG_ERROR, 54 | "%s(*):cannot open server socket.", 55 | name_.c_str()); 56 | return -1; 57 | } 58 | 59 | thread_ctrl_ = true; 60 | thread_ = std::thread(&GepClient::RunThread, this); 61 | gep_log(LOG_WARNING, 62 | "%s(*):thread started", name_.c_str()); 63 | return 0; 64 | } 65 | 66 | void GepClient::Stop() { 67 | gep_log(LOG_WARNING, 68 | "%s(*):kill thread", name_.c_str()); 69 | thread_ctrl_ = false; 70 | thread_.join(); 71 | 72 | // closing GEP channel 73 | gep_channel_->Close(); 74 | reconnect_count_ = 0; 75 | } 76 | 77 | void GepClient::Reconnect() { 78 | // try to reconnect 79 | gep_log(LOG_WARNING, 80 | "%s(*):reconnecting to server socket.", 81 | name_.c_str()); 82 | if (gep_channel_->OpenClientSocket() < 0) { 83 | gep_log(LOG_ERROR, 84 | "%s(*):cannot open server socket.", 85 | name_.c_str()); 86 | sleep(kReconnectRetryDelaySecs); 87 | } else { 88 | gep_log(LOG_WARNING, 89 | "%s(*):reconnected.", name_.c_str()); 90 | reconnect_count_++; 91 | } 92 | } 93 | 94 | int GepClient::Send(const GepProtobufMessage &msg) { 95 | return gep_channel_->SendMessage(msg); 96 | } 97 | 98 | void GepClient::RunThread() { 99 | int max_fds; 100 | fd_set read_fds; 101 | pid_t tid = syscall(__NR_gettid); 102 | 103 | gep_log(LOG_DEBUG, 104 | "%s(*):service thread is running (tid:%d)", 105 | name_.c_str(), tid); 106 | 107 | int socket = gep_channel_->GetSocket(); 108 | while (GetThreadCtrl()) { 109 | if (socket == -1) { 110 | Reconnect(); 111 | socket = gep_channel_->GetSocket(); 112 | continue; 113 | } 114 | FD_ZERO(&read_fds); 115 | FD_SET(socket, &read_fds); 116 | max_fds = socket; 117 | 118 | // Calculate the select timeout. 119 | int64_t select_timeout_usec = proto_->GetSelectTimeoutUsec(); 120 | 121 | struct timeval select_timeout = usecs_to_timeval(select_timeout_usec); 122 | int status = select(max_fds + 1, &read_fds, NULL, NULL, &select_timeout); 123 | if (status < 0 && errno != EINTR) { 124 | gep_perror(errno, "%s(*):Error-service socket select-", 125 | name_.c_str()); 126 | break; 127 | } 128 | 129 | if (!GetThreadCtrl()) break; 130 | 131 | // Handle incoming requests from the server and check for timeout 132 | if (FD_ISSET(socket, &read_fds)) { 133 | int res = gep_channel_->RecvData(); 134 | if (res < 0) { 135 | // on any receive error, toss the existing connection and try to 136 | // reconnect 137 | gep_log(LOG_WARNING, 138 | "%s(*):connection reset by peer.", 139 | name_.c_str()); 140 | gep_channel_->Close(); 141 | socket = -1; 142 | } 143 | } 144 | } // endof while (GetThreadCtrl()) 145 | 146 | gep_log(LOG_WARNING, 147 | "%s(*):service thread is exiting (tid:%d)", 148 | name_.c_str(), tid); 149 | } 150 | -------------------------------------------------------------------------------- /src/gep_protocol.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP protocol implementation. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | #include "gep_protocol.h" 14 | 15 | #include // for TextFormat 16 | #include // for htonl 17 | 18 | #include "gep_common.h" // for GepProtobufMessage 19 | #include "utils.h" // for SET_UINT32, UINT32, snprintf_printable 20 | 21 | using namespace libgep_utils; 22 | 23 | const int64_t kDefaultSelectTimeUsec = secs_to_usecs(1); 24 | 25 | GepProtocol::GepProtocol(int port) 26 | : port_(port), 27 | mode_(kMode), 28 | magic_(kMagic), 29 | select_timeout_usec_(kDefaultSelectTimeUsec) { 30 | } 31 | 32 | GepProtocol::~GepProtocol() { 33 | } 34 | 35 | // descriptor/tag converters 36 | int GepProtocol::TagString(uint32_t tag, char *buf, int max_buf) { 37 | uint32_t tag_n = htonl(tag); 38 | return snprintf_printable(buf, max_buf, 39 | reinterpret_cast(&tag_n), 4); 40 | } 41 | 42 | void GepProtocol::SetSelectTimeoutUsec(int64_t select_timeout_usec) { 43 | select_timeout_usec_ = select_timeout_usec; 44 | } 45 | 46 | bool GepProtocol::ScanHeader(uint8_t *buf, uint32_t *tag, uint32_t *value_len) { 47 | // read TL in TLV 48 | uint32_t magic = UINT32(buf + kOffsetMagic); 49 | *tag = UINT32(buf + kOffsetTag); 50 | *value_len = UINT32(buf + kOffsetLen); 51 | 52 | // check the magic number 53 | return (magic == magic_); 54 | } 55 | 56 | void GepProtocol::PrintHeader(uint32_t tag, uint32_t value_len, uint8_t *buf) { 57 | SET_UINT32(buf + kOffsetMagic, magic_); 58 | SET_UINT32(buf + kOffsetTag, tag); 59 | SET_UINT32(buf + kOffsetLen, value_len); 60 | } 61 | 62 | bool GepProtocol::Serialize(const GepProtobufMessage &msg, std::string *s) { 63 | #ifndef GEP_LITE 64 | if (mode_ == MODE_TEXT) { 65 | return (google::protobuf::TextFormat::PrintToString(msg, s)); 66 | } else { // mode_ == MODE_BINARY 67 | #endif 68 | return msg.SerializeToString(s); 69 | #ifndef GEP_LITE 70 | } 71 | #endif 72 | } 73 | 74 | bool GepProtocol::Unserialize(const std::string &s, GepProtobufMessage *msg) { 75 | #ifndef GEP_LITE 76 | if (mode_ == MODE_TEXT) { 77 | return (google::protobuf::TextFormat::ParseFromString(s, msg)); 78 | } else { // mode_ == MODE_BINARY 79 | #endif 80 | msg->Clear(); 81 | if (!s.empty()) 82 | return msg->ParseFromString(s); 83 | return true; 84 | #ifndef GEP_LITE 85 | } 86 | #endif 87 | } 88 | -------------------------------------------------------------------------------- /src/gep_server.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP protocol: server implementation. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "gep_server.h" 15 | 16 | #include // for errno, EINTR 17 | #include // for int64_t 18 | #include // for NULL 19 | #include // for FD_ISSET, FD_SET, select, etc 20 | #include // for timeval 21 | #include // for __NR_gettid 22 | #include // NOLINT 23 | #include // for syscall, pid_t 24 | 25 | #include "gep_channel_array.h" // for GepChannelArray 26 | #include "gep_common.h" // for GepProtobufMessage 27 | #include "utils.h" // for MAX 28 | 29 | using namespace libgep_utils; 30 | 31 | GepServer::GepServer(const std::string &name, int max_channels, 32 | void *context, GepProtocol *proto, const GepVFT* ops) 33 | : name_(name), 34 | context_(context), 35 | proto_(proto), 36 | ops_(ops), 37 | thread_ctrl_(false) { 38 | // init GEP channel array 39 | gep_channel_array_ = new GepChannelArray("gep_channel_array", this, proto_, 40 | max_channels, ops_, context_); 41 | } 42 | 43 | GepServer::~GepServer() { 44 | // close and free all the GEP channels 45 | delete gep_channel_array_; 46 | delete proto_; 47 | } 48 | 49 | int GepServer::Start() { 50 | if (gep_channel_array_->OpenServerSocket() < 0) 51 | return -1; 52 | 53 | thread_ctrl_ = true; 54 | thread_ = std::thread(&GepServer::RunThread, this); 55 | gep_log(LOG_WARNING, 56 | "%s(*):thread started", name_.c_str()); 57 | return 0; 58 | } 59 | 60 | void GepServer::Stop() { 61 | gep_log(LOG_WARNING, 62 | "%s(*):kill thread", name_.c_str()); 63 | thread_ctrl_ = false; 64 | thread_.join(); 65 | 66 | // closing all channels and sockets 67 | gep_channel_array_->ClearGepChannelVector(); 68 | } 69 | 70 | void GepServer::RunThread() { 71 | int max_fds; 72 | fd_set read_fds; 73 | pid_t tid = syscall(__NR_gettid); 74 | 75 | gep_log(LOG_DEBUG, 76 | "%s(*):service thread is running (tid:%d)", 77 | name_.c_str(), tid); 78 | 79 | int server_socket = gep_channel_array_->GetServerSocket(); 80 | if (server_socket < 0) { 81 | gep_log(LOG_ERROR, 82 | "%s(*):Error-invalid server socket", 83 | name_.c_str()); 84 | return; 85 | } 86 | 87 | while (GetThreadCtrl()) { 88 | FD_ZERO(&read_fds); 89 | FD_SET(server_socket, &read_fds); 90 | max_fds = server_socket; 91 | gep_channel_array_->GetVectorReadFds(&max_fds, &read_fds); 92 | 93 | // Calculate the select timeout. 94 | int64_t select_timeout_usec = proto_->GetSelectTimeoutUsec(); 95 | struct timeval select_timeout = usecs_to_timeval(select_timeout_usec); 96 | 97 | int status = select(max_fds + 1, &read_fds, NULL, NULL, &select_timeout); 98 | if (status < 0 && errno != EINTR) { 99 | gep_perror(errno, "%s(*):Error-service socket select-", 100 | name_.c_str()); 101 | break; 102 | } 103 | 104 | if (!GetThreadCtrl()) break; 105 | 106 | // process all inputs 107 | gep_channel_array_->RecvData(&read_fds); 108 | 109 | if (!GetThreadCtrl()) break; 110 | 111 | // accept new GEP channel connections (from GEP clients) 112 | if (FD_ISSET(server_socket, &read_fds)) 113 | if (gep_channel_array_->AcceptConnection() < 0) 114 | break; 115 | } // while (GetThreadCtrl()) 116 | 117 | gep_log(LOG_WARNING, 118 | "%s(*):service thread is exiting (tid:%d)", 119 | name_.c_str(), tid); 120 | } 121 | 122 | int GepServer::Send(const GepProtobufMessage &msg) { 123 | return gep_channel_array_->SendMessage(msg); 124 | } 125 | 126 | int GepServer::Send(const GepProtobufMessage &msg, int id) { 127 | return gep_channel_array_->SendMessage(msg, id); 128 | } 129 | -------------------------------------------------------------------------------- /src/raw_socket_interface.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _RAW_SOCKET_INTERFACE_H_ 4 | #define _RAW_SOCKET_INTERFACE_H_ 5 | 6 | #include 7 | #include // for int64_t 8 | #include 9 | #include 10 | 11 | class RawSocketInterface { 12 | public: 13 | virtual ~RawSocketInterface() = default; 14 | 15 | virtual int Socket(int domain, int type, int protocol) { 16 | return socket(domain, type, protocol); 17 | } 18 | virtual int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 19 | return bind(sockfd, addr, addrlen); 20 | } 21 | virtual int Listen(int sockfd, int backlog) { 22 | return listen(sockfd, backlog); 23 | } 24 | virtual int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { 25 | return accept(sockfd, addr, addrlen); 26 | } 27 | virtual ssize_t Recv(int sockfd, void *buf, size_t len, int flags) { 28 | return recv(sockfd, buf, len, flags); 29 | } 30 | virtual ssize_t Send(int sockfd, const void *buf, size_t len, int flags) { 31 | return send(sockfd, buf, len, flags); 32 | } 33 | virtual int Select(int nfds, fd_set *readfds, fd_set *writefds, 34 | fd_set *exceptfds, struct timeval *timeout) { 35 | return select(nfds, readfds, writefds, exceptfds, timeout); 36 | } 37 | virtual int GetSockOpt(int sockfd, int level, int optname, 38 | void *optval, socklen_t *optlen) { 39 | return getsockopt(sockfd, level, optname, optval, optlen); 40 | } 41 | virtual int SetSockOpt(int sockfd, int level, int optname, 42 | const void *optval, socklen_t optlen) { 43 | return setsockopt(sockfd, level, optname, optval, optlen); 44 | } 45 | virtual int GetSockName(int sockfd, struct sockaddr *addr, 46 | socklen_t *addrlen) { 47 | return getsockname(sockfd, addr, addrlen); 48 | } 49 | virtual int GetPeerName(int sockfd, struct sockaddr *addr, 50 | socklen_t *addrlen) { 51 | return getpeername(sockfd, addr, addrlen); 52 | } 53 | }; 54 | 55 | #endif // _RAW_SOCKET_INTERFACE_H_ 56 | -------------------------------------------------------------------------------- /src/socket_interface.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _GNU_SOURCE 4 | #define _GNU_SOURCE 5 | #endif 6 | 7 | #ifdef __cplusplus 8 | #define __STDC_FORMAT_MACROS 9 | #endif 10 | 11 | #include "socket_interface.h" 12 | 13 | #include // for inet_ntop 14 | #include // for errno, EAGAIN, EWOULDBLOCK 15 | #include // for fcntl 16 | #include // for sockaddr_in, IPPROTO_TCP, etc 17 | #include // for TCP_NODELAY 18 | #include // for int64_t, uint8_t 19 | #include // for NULL 20 | #include // for memset 21 | #include // for FD_SET, FD_ZERO, etc 22 | #include // for SOL_SOCKET, etc 23 | #include // for timeval 24 | 25 | #include "raw_socket_interface.h" 26 | #include "utils.h" 27 | 28 | using namespace libgep_utils; 29 | 30 | int SocketInterface::FullSend(int fd, const uint8_t* buf, int size, 31 | int64_t timeout_ms) { 32 | int64_t started_ms = time_manager_->ms_elapse(0); 33 | int total_sent = 0; 34 | 35 | while (total_sent < size) { 36 | int count = raw_socket_interface_->Send(fd, buf + total_sent, 37 | size - total_sent, MSG_DONTWAIT); 38 | if (count > 0) { 39 | total_sent += count; 40 | if (total_sent >= size) { 41 | break; 42 | } 43 | // check whether we have timed out 44 | if (timeout_ms < time_manager_->ms_elapse(started_ms)) { 45 | return 0; // timed out 46 | } 47 | continue; 48 | } 49 | if (count == 0) { 50 | // orderly shutdown of the remote side 51 | return -2; 52 | } 53 | if (count < 0 && errno != EAGAIN && errno != EWOULDBLOCK) { 54 | return -1; 55 | } 56 | 57 | // EAGAIN/EWOULDBLOCK, use select to sleep until the socket has space 58 | int64_t sleeptime_ms = timeout_ms - time_manager_->ms_elapse(started_ms); 59 | if (sleeptime_ms < 0) { 60 | return 0; // timed out 61 | } 62 | 63 | struct timeval tv; 64 | memset(&tv, 0, sizeof(tv)); 65 | tv.tv_sec = sleeptime_ms / 1000; 66 | tv.tv_usec = (sleeptime_ms % 1000) * 1000; 67 | fd_set write_fds; 68 | FD_ZERO(&write_fds); 69 | FD_SET(fd, &write_fds); 70 | 71 | int num = raw_socket_interface_->Select(fd + 1, NULL, &write_fds, NULL, 72 | &tv); 73 | if (num == 0) { 74 | return 0; // timed out 75 | } 76 | } 77 | return total_sent; 78 | } 79 | 80 | int SocketInterface::SetNonBlocking(const char *log_module, int sock) { 81 | if (!log_module) log_module = "unknown"; 82 | 83 | int flags = fcntl(sock, F_GETFL, 0); 84 | if (flags < 0) { 85 | gep_perror(errno, "%s():Error-Cannot GETFL on socket (%d)-", 86 | log_module, sock); 87 | return -1; 88 | } 89 | 90 | if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) < 0) { 91 | gep_perror(errno, "%s():Error-Cannot SETFL on socket (%d)-", log_module, 92 | sock); 93 | return -1; 94 | } 95 | return 0; 96 | } 97 | 98 | int SocketInterface::SetPriority(const char *log_module, int sock, int prio) { 99 | if (!log_module) log_module = "unknown"; 100 | 101 | if (raw_socket_interface_->SetSockOpt(sock, SOL_SOCKET, SO_PRIORITY, 102 | &prio, sizeof(prio)) == -1) { 103 | gep_perror(errno, "%s():Error-Cannot set SO_PRIORITY to %d on socket " 104 | "(%d)", log_module, prio, sock); 105 | return -1; 106 | } 107 | return 0; 108 | } 109 | 110 | int SocketInterface::SetNoDelay(const char *log_module, int sock) { 111 | if (!log_module) log_module = "unknown"; 112 | 113 | int flags = 1; 114 | if (raw_socket_interface_->SetSockOpt(sock, IPPROTO_TCP, TCP_NODELAY, 115 | &flags, sizeof(flags)) < 0) { 116 | gep_perror(errno, "%s():Error-Cannot set TCP_NODELAY on socket (%d)-", 117 | log_module, sock); 118 | return -1; 119 | } 120 | return 0; 121 | } 122 | 123 | int SocketInterface::SetReuseAddr(const char *log_module, int sock) { 124 | if (!log_module) log_module = "unknown"; 125 | 126 | int flags = 1; 127 | if (raw_socket_interface_->SetSockOpt(sock, SOL_SOCKET, SO_REUSEADDR, 128 | &flags, sizeof(flags)) < 0) { 129 | gep_perror(errno, "%s():Error-Cannot set SO_REUSEADDR on socket (%d)-", 130 | log_module, sock); 131 | return -1; 132 | } 133 | return 0; 134 | } 135 | 136 | int SocketInterface::GetPort(const char *log_module, int sock, int *port) { 137 | struct sockaddr_in addr; 138 | socklen_t addrlen = sizeof(addr); 139 | 140 | if (raw_socket_interface_->GetSockName(sock, (struct sockaddr*)&addr, 141 | &addrlen) < 0) { 142 | gep_perror(errno, "%s(*):Error-getsockname failed on socket %d-", 143 | log_module, sock); 144 | return -1; 145 | } 146 | 147 | *port = ntohs(addr.sin_port); 148 | return 0; 149 | } 150 | 151 | char *SocketInterface::GetPeerIP(int sock, char *buf, int size) { 152 | struct sockaddr_in sock_addr; 153 | socklen_t sockaddr_size = sizeof(sock_addr); 154 | 155 | if (raw_socket_interface_->GetPeerName(sock, (struct sockaddr *)&sock_addr, 156 | &sockaddr_size) == 0) { 157 | if (inet_ntop(AF_INET, &sock_addr.sin_addr, buf, size)) { 158 | return buf; 159 | } else { 160 | gep_perror(errno, "util():Error-Cannot determine peer-IP-"); 161 | } 162 | } 163 | 164 | // error case 165 | nice_snprintf(buf, size, "%s", "unknown"); 166 | return buf; 167 | } 168 | -------------------------------------------------------------------------------- /src/socket_interface.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _SOCKET_INTERFACE_H_ 4 | #define _SOCKET_INTERFACE_H_ 5 | 6 | #include 7 | #include // for int64_t 8 | #include 9 | #include 10 | 11 | #include // for unique_ptr 12 | 13 | #include "raw_socket_interface.h" 14 | #include "time_manager.h" 15 | 16 | 17 | class SocketInterface { 18 | public: 19 | SocketInterface() { 20 | raw_socket_interface_.reset(new RawSocketInterface()); 21 | time_manager_.reset(new TimeManager()); 22 | } 23 | virtual ~SocketInterface() = default; 24 | 25 | virtual int Socket(int domain, int type, int protocol) { 26 | return raw_socket_interface_->Socket(domain, type, protocol); 27 | } 28 | virtual int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 29 | return raw_socket_interface_->Bind(sockfd, addr, addrlen); 30 | } 31 | virtual int Listen(int sockfd, int backlog) { 32 | return raw_socket_interface_->Listen(sockfd, backlog); 33 | } 34 | virtual int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { 35 | return raw_socket_interface_->Accept(sockfd, addr, addrlen); 36 | } 37 | 38 | // non-blocking send/recv 39 | // Sends the given data until the socket accepted it all or timeout was hit. 40 | // Returns the number of bytes sent (=size) for success, else 41 | // 0 for timeout 42 | // -1 for error 43 | // -2 if the connection was orderly shutdown 44 | virtual int FullSend(int fd, const uint8_t* buf, int size, 45 | int64_t timeout_ms); 46 | // TODO(chema): replace with FullRecv() 47 | virtual ssize_t Recv(int sockfd, void *buf, size_t len, int flags) { 48 | return raw_socket_interface_->Recv(sockfd, buf, len, flags); 49 | } 50 | 51 | // Sets or gets various settings on the given socket. 52 | // Returns -1 for error, else 0. 53 | virtual int SetNonBlocking(const char *log_module, int sock); 54 | virtual int SetPriority(const char *log_module, int sock, int prio); 55 | virtual int SetNoDelay(const char *log_module, int sock); 56 | virtual int SetReuseAddr(const char *log_module, int sock); 57 | virtual int GetPort(const char *log_module, int sock, int *port); 58 | // other socket-related functions 59 | virtual char *GetPeerIP(int sock, char *buf, int size); 60 | 61 | private: 62 | friend class TestableSocketInterface; 63 | 64 | std::unique_ptr raw_socket_interface_; 65 | std::unique_ptr time_manager_; 66 | }; 67 | 68 | #endif // _SOCKET_INTERFACE_H_ 69 | -------------------------------------------------------------------------------- /src/time_manager.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "time_manager.h" 4 | 5 | #include // for uint64_t 6 | #include // for CLOCK_MONOTONIC 7 | #include 8 | #include 9 | 10 | #include "utils.h" // for kNsecsPerSec 11 | 12 | 13 | using namespace libgep_utils; 14 | 15 | static inline uint64_t get_now_ns() { 16 | struct timespec tv; 17 | clock_gettime(CLOCK_MONOTONIC, &tv); 18 | return tv.tv_sec * kNsecsPerSec + tv.tv_nsec; 19 | } 20 | 21 | uint64_t TimeManager::ms_elapse(uint64_t start_time_ms) { 22 | return (get_now_ns() - start_time_ms * kNsecsPerMsec) / kNsecsPerMsec; 23 | } 24 | 25 | void TimeManager::ms_sleep(uint64_t msecs) { 26 | ns_sleep(kNsecsPerMsec * msecs); 27 | } 28 | 29 | void TimeManager::us_sleep(uint64_t usecs) { 30 | ns_sleep(kNsecsPerUsec * usecs); 31 | } 32 | 33 | void TimeManager::ns_sleep(uint64_t nsecs) { 34 | struct timespec tv; 35 | // tv_nsec field must be [0..kNsecsPerSec-1] 36 | tv.tv_sec = nsecs / kNsecsPerSec; 37 | tv.tv_nsec = nsecs % kNsecsPerSec; 38 | nanosleep(&tv, NULL); 39 | } 40 | -------------------------------------------------------------------------------- /src/time_manager.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _SRC_TIME_MANAGER_H_ 4 | #define _SRC_TIME_MANAGER_H_ 5 | 6 | #include // for uint64_t 7 | 8 | // Time manager object, providing *elapse() and *_sleep() functions that 9 | // can be faked/mocked. 10 | class TimeManager { 11 | public: 12 | TimeManager() = default; 13 | virtual ~TimeManager() = default; 14 | 15 | virtual uint64_t ms_elapse(uint64_t start_time_ms); 16 | 17 | virtual void ms_sleep(uint64_t msecs); 18 | virtual void ns_sleep(uint64_t nsecs); 19 | virtual void us_sleep(uint64_t usecs); 20 | }; 21 | 22 | #endif // _SRC_TIME_MANAGER_H_ 23 | -------------------------------------------------------------------------------- /src/utils.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _GNU_SOURCE 4 | #define _GNU_SOURCE 5 | #endif 6 | 7 | #ifdef __cplusplus 8 | #define __STDC_FORMAT_MACROS 9 | #endif 10 | 11 | #include "utils.h" 12 | 13 | #include // for isprint 14 | #include // for va_list 15 | #include // for printf, snprintf, fflush, etc 16 | #include // for memset, strerror_r 17 | #include // for timeval, gettimeofday, etc 18 | #include // for NULL, strftime, tm, etc 19 | #include // for string, operator==, etc 20 | 21 | #include "gep_common.h" // for GepProtobufMessage 22 | 23 | namespace libgep_utils { 24 | 25 | LogLevel gep_log_level = LOG_WARNING; 26 | 27 | void gep_log_set_level(LogLevel level) { 28 | gep_log_level = level; 29 | } 30 | 31 | LogLevel gep_log_get_level() { 32 | return gep_log_level; 33 | } 34 | 35 | void gep_log(LogLevel level, const char* cstr, ...) { 36 | va_list va; 37 | 38 | if (level > gep_log_level) 39 | return; 40 | 41 | char date_str[kDateStringLen]; 42 | snprintf_date(date_str, kDateStringLen, NULL, false); 43 | printf("%s ", date_str); 44 | 45 | const char *fmt = cstr; 46 | va_start(va, cstr); 47 | vprintf(fmt, va); 48 | va_end(va); 49 | 50 | printf("\n"); 51 | fflush(stdout); 52 | } 53 | 54 | void gep_perror(int err, const char* cstr, ...) { 55 | va_list va; 56 | 57 | char date_str[kDateStringLen]; 58 | snprintf_date(date_str, kDateStringLen, NULL, false); 59 | printf("%s ", date_str); 60 | 61 | const char *fmt = cstr; 62 | va_start(va, cstr); 63 | vprintf(fmt, va); 64 | va_end(va); 65 | 66 | char strerrbuf[1024] = {'\0'}; 67 | printf(" '%s'[%d]", strerror_r(err, strerrbuf, sizeof(strerrbuf)), err); 68 | 69 | printf("\n"); 70 | fflush(stdout); 71 | } 72 | 73 | bool ProtobufEqual(const GepProtobufMessage &msg1, 74 | const GepProtobufMessage &msg2) { 75 | std::string str_msg1; 76 | std::string str_msg2; 77 | msg1.SerializeToString(&str_msg1); 78 | msg2.SerializeToString(&str_msg2); 79 | return str_msg1 == str_msg2; 80 | } 81 | 82 | int64_t GetUnixTimeUsec() { 83 | struct timeval tv; 84 | gettimeofday(&tv, NULL); 85 | return ((int64_t) tv.tv_sec * kUsecsPerSec) + tv.tv_usec; 86 | } 87 | 88 | int nice_snprintf(char *str, size_t size, const char *format, ...) { 89 | va_list ap; 90 | int bi; 91 | va_start(ap, format); 92 | // http://stackoverflow.com/a/100991 93 | bi = vsnprintf(str, size, format, ap); 94 | va_end(ap); 95 | if (bi > size) { 96 | // From printf(3): 97 | // "snprintf() [returns] the number of characters (not including the 98 | // trailing '\0') which would have been written to the final string 99 | // if enough space had been available" [printf(3)] 100 | bi = size; 101 | } 102 | return bi; 103 | } 104 | 105 | /* snprintf's bytes in hex */ 106 | int snprintf_hex(char *buf, int bufsize, const uint8_t *data, int len) { 107 | int i; 108 | int bi = 0; 109 | 110 | // ensure we always return something valid 111 | buf[0] = '\0'; 112 | for (i = 0; i < len && bi < bufsize; ++i) { 113 | bi += nice_snprintf(buf+bi, bufsize-bi, "%02x", *(data + i)); 114 | } 115 | return bi; 116 | } 117 | 118 | /* snprintf's bytes in printable characters */ 119 | int snprintf_printable(char *buf, int bufsize, const uint8_t *data, int len) { 120 | int i; 121 | int bi = 0; 122 | 123 | // ensure we always return something valid 124 | buf[0] = '\0'; 125 | for (i = 0; i < len && bi < bufsize; ++i) { 126 | if (isprint(*(data + i))) 127 | bi += nice_snprintf(buf+bi, bufsize-bi, "%c", *(data + i)); 128 | else 129 | bi += nice_snprintf(buf+bi, bufsize-bi, "\\x%02x", *(data + i)); 130 | } 131 | return bi; 132 | } 133 | 134 | /* snprintf's date (use tv=NULL for current) in a standard format (iso 8601) */ 135 | int snprintf_date(char *buf, int bufsize, const struct timeval *tvin, 136 | bool full) { 137 | int bi = 0; 138 | struct timeval tv; 139 | 140 | if (tvin == NULL) { 141 | // get the current time 142 | gettimeofday(&tv, NULL); 143 | } else { 144 | tv.tv_sec = tvin->tv_sec; 145 | tv.tv_usec = tvin->tv_usec; 146 | } 147 | 148 | // get the timezone 149 | struct tm ltm; 150 | localtime_r(&tv.tv_sec, <m); 151 | bool is_dst = (ltm.tm_isdst > 0); 152 | 153 | // adjust timeval to Pacific Time (PST or UTC-08, PDT or UTC-07) 154 | if (is_dst) 155 | tv.tv_sec -= 3600 * 7; 156 | else 157 | tv.tv_sec -= 3600 * 8; 158 | 159 | // convert it to a calendar time (UTC-based) 160 | gmtime_r(&tv.tv_sec, <m); 161 | 162 | // adjust the time zone 163 | if (is_dst) 164 | ltm.tm_gmtoff -= 3600 * 7; 165 | else 166 | ltm.tm_gmtoff -= 3600 * 8; 167 | 168 | // convert it to a string 169 | int ret; 170 | if (full) 171 | ret = strftime(buf+bi, bufsize-bi, "%FT%T", <m); 172 | else 173 | ret = strftime(buf+bi, bufsize-bi, "%d,%T", <m); 174 | if (ret <= 0) { 175 | // note that strftime return code is not like snprintf's: "If the 176 | // length of the result string (including the terminating null byte) 177 | // would exceed max bytes, then strftime() returns 0, and the contents 178 | // of the array are undefined." 179 | buf[bi] = '\0'; 180 | return bi; 181 | } 182 | bi += ret; 183 | 184 | bi += nice_snprintf(buf+bi, bufsize-bi, ".%03d", 185 | static_cast(tv.tv_usec / 1000)); 186 | if (full) { 187 | ret = strftime(buf+bi, bufsize-bi, "%z", <m); 188 | if (ret <= 0) 189 | buf[bi] = '\0'; 190 | else 191 | bi += ret; 192 | } 193 | 194 | return bi; 195 | } 196 | 197 | } // namespace libgep_utils 198 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _UTILS_H_ 4 | #define _UTILS_H_ 5 | 6 | #include // for int64_t, uint8_t, uint16_t, etc 7 | #include // for timeval 8 | 9 | #include "gep_common.h" // for GepProtobufMessage 10 | 11 | namespace libgep_utils { 12 | 13 | #define UINT64(x) \ 14 | (((uint64_t)*((x) + 0) << 56) | ((uint64_t)*((x) + 1) << 48) | \ 15 | ((uint64_t)*((x) + 2) << 40) | ((uint64_t)*((x) + 3) << 32) | \ 16 | ((uint64_t)*((x) + 4) << 24) | ((uint64_t)*((x) + 5) << 16) | \ 17 | ((uint64_t)*((x) + 6) << 8) | ((uint64_t)*((x) + 7))) 18 | #define INT64(x) (int64_t)UINT64(x) 19 | #define UINT32(x) \ 20 | (((uint32_t)*((x) + 0) << 24) | ((uint32_t)*((x) + 1) << 16) | \ 21 | ((uint32_t)*((x) + 2) << 8) | ((uint32_t)*((x) + 3))) 22 | #define INT32(x) (int32_t)UINT32(x) 23 | #define UINT16(x) (((uint32_t)*(x) << 8) | ((uint32_t)*((x) + 1))) 24 | #define INT16(x) (int16_t)UINT16(x) 25 | 26 | #define SET_UINT8(x, y) \ 27 | { \ 28 | *((x)) = (uint8_t)((y)); \ 29 | } 30 | #define SET_UINT16(x, y) \ 31 | { \ 32 | *((x)) = (uint8_t)((y) >> 8); \ 33 | *((x) + 1) = (uint8_t)((y)); \ 34 | } 35 | #define SET_UINT32(x, y) \ 36 | { \ 37 | *((x)) = (uint8_t)((y) >> 24); \ 38 | *((x) + 1) = (uint8_t)((y) >> 16); \ 39 | *((x) + 2) = (uint8_t)((y) >> 8); \ 40 | *((x) + 3) = (uint8_t)((y)); \ 41 | } 42 | #define SET_UINT64(x, y) \ 43 | { \ 44 | *((x)) = (uint8_t)((y) >> 56); \ 45 | *((x) + 1) = (uint8_t)((y) >> 48); \ 46 | *((x) + 2) = (uint8_t)((y) >> 40); \ 47 | *((x) + 3) = (uint8_t)((y) >> 32); \ 48 | *((x) + 4) = (uint8_t)((y) >> 24); \ 49 | *((x) + 5) = (uint8_t)((y) >> 16); \ 50 | *((x) + 6) = (uint8_t)((y) >> 8); \ 51 | *((x) + 7) = (uint8_t)((y)); \ 52 | } 53 | 54 | typedef enum { 55 | LOG_ERROR = 1, 56 | LOG_WARNING = 2, 57 | LOG_DEBUG = 3, 58 | } LogLevel; 59 | 60 | void gep_log_set_level(LogLevel level); 61 | LogLevel gep_log_get_level(); 62 | 63 | void gep_log(LogLevel level, const char* cstr, ...) 64 | __attribute__((format(printf, 2, 3))); 65 | 66 | void gep_perror(int err, const char* cstr, ...) 67 | __attribute__((format(printf, 2, 3))); 68 | 69 | 70 | bool ProtobufEqual(const GepProtobufMessage &msg1, 71 | const GepProtobufMessage &msg2); 72 | 73 | const int64_t kMsecsPerSec = 1000LL; 74 | const int64_t kUsecsPerSec = 1000000LL; 75 | const int64_t kUsecsPerMsec = 1000LL; 76 | const int64_t kNsecsPerSec = 1000000000LL; 77 | const int64_t kNsecsPerUsec = 1000LL; 78 | const int64_t kNsecsPerMsec = 1000000LL; 79 | const int64_t kOneDayInSec = 24 * 60 * 60; 80 | 81 | const int64_t kUnixTimeInvalid = -1; 82 | 83 | static inline int64_t secs_to_msecs(int64_t secs) { 84 | return secs * kMsecsPerSec; 85 | } 86 | 87 | static inline int64_t msecs_to_secs(int64_t msecs) { 88 | return msecs / kMsecsPerSec; 89 | } 90 | 91 | static inline int64_t secs_to_usecs(int64_t secs) { 92 | return secs * kUsecsPerSec; 93 | } 94 | 95 | static inline int64_t usecs_to_secs(int64_t usecs) { 96 | return usecs / kUsecsPerSec; 97 | } 98 | 99 | static inline int64_t msecs_to_usecs(int64_t msecs) { 100 | return msecs * kUsecsPerMsec; 101 | } 102 | 103 | static inline int64_t usecs_to_msecs(int64_t usecs) { 104 | return usecs / kUsecsPerMsec; 105 | } 106 | 107 | static inline int64_t usecs_to_nsecs(int64_t usecs) { 108 | return usecs * kNsecsPerUsec; 109 | } 110 | 111 | static inline int64_t nsecs_to_usecs(int64_t nsecs) { 112 | return nsecs / kNsecsPerUsec; 113 | } 114 | 115 | static inline int64_t timeval_to_usecs(const struct timeval *tv) { 116 | return (secs_to_usecs(tv->tv_sec) + tv->tv_usec); 117 | } 118 | 119 | static inline struct timeval usecs_to_timeval(int64_t usecs) { 120 | struct timeval out; 121 | out.tv_sec = usecs / kUsecsPerSec; 122 | out.tv_usec = (usecs % kUsecsPerSec); 123 | return out; 124 | } 125 | 126 | // Returns the current timestamp as an int64_t (microseconds since unix epoch). 127 | int64_t GetUnixTimeUsec(); 128 | 129 | // Returns the current timestamp as an int64_t (seconds since unix epoch). 130 | int64_t GetUnixTimeSec(); 131 | 132 | // Fills the given buffer with a character string of the peer IP address 133 | // of the given socket. On error it inserts the string "unknown". In both 134 | // cases it returns a pointer to the beginning of the buffer. 135 | char *get_peer_ip(int sock, char *buf, int size); 136 | 137 | // A version of snprintf() that, when it cannot write enough characters, 138 | // returns the number of characters actually printed. Compare with 139 | // (vanilla) snprintf(), which returns "the number of characters (not 140 | // including the trailing '\0') which would have been written to the 141 | // final string if enough space had been available." [printf(3)] 142 | // See http://stackoverflow.com/a/28295939. 143 | int nice_snprintf(char *str, size_t size, const char *format, ...); 144 | 145 | // snprintf's bytes in hex 146 | int snprintf_hex(char *buf, int bufsize, const uint8_t *data, int len); 147 | 148 | // Prints a printable string into a buffer. 149 | int snprintf_printable(char *buf, int bufsize, const uint8_t *data, int len); 150 | 151 | const int kDateStringLen = 64; 152 | // Prints date (use tv=NULL for current) in a standard format (iso 8601). 153 | // Set "full" to true to print the whole date, false for a concise version. 154 | int snprintf_date(char *buf, int bufsize, const struct timeval *tv, bool full); 155 | 156 | } // namespace libgep_utils 157 | 158 | #endif // _UTILS_H_ 159 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright Google Inc. Apache 2.0. 2 | 3 | TOP:=.. 4 | 5 | TEST_TARGETS_FULL= \ 6 | utils_test \ 7 | gep_protocol_test \ 8 | gep_channel_test \ 9 | gep_channel_array_test \ 10 | gep_client_test \ 11 | gep_server_test \ 12 | gep_end_to_end_test \ 13 | socket_interface_test 14 | 15 | TEST_TARGETS_LITE= \ 16 | gep_protocol_test_lite \ 17 | gep_channel_test_lite \ 18 | gep_channel_array_test_lite \ 19 | gep_client_test_lite \ 20 | gep_server_test_lite \ 21 | gep_end_to_end_test_lite 22 | 23 | TEST_TARGETS= $(TEST_TARGETS_FULL) $(TEST_TARGETS_LITE) 24 | 25 | # add the local gep libraries info before the hostdir ones get added 26 | CPPFLAGS+=-I../include -I../src $(PROTO_CPPFLAGS) 27 | LDFLAGS+=-L../src 28 | 29 | include ../rules.mk 30 | 31 | CPPFLAGS+=-I. -I.. -I../include 32 | 33 | all: .protos_done 34 | $(MAKE) all_for_real_this_time 35 | 36 | all_for_real_this_time: $(TARGETS) 37 | 38 | .protos_done: test.proto test_lite.proto 39 | $(MAKE) test.pb.h 40 | $(MAKE) test_lite.pb.h 41 | 42 | test.pb.h: test.proto 43 | echo "Building test.pb.h" 44 | $(HOST_PROTOC) $(PROTOC_FLAGS) $< 45 | 46 | test_lite.pb.h: test_lite.proto 47 | echo "Building test_lite.pb.h" 48 | $(HOST_PROTOC) $(PROTOC_FLAGS) $< 49 | 50 | $(TEST_TARGETS_FULL) : \ 51 | LIBS+=$(PROTOFULL_LDFLAGS) -lgtest -L../src -lgepserver -lgepclient 52 | 53 | $(TEST_TARGETS_LITE) : \ 54 | LIBS+=$(PROTOLITE_LDFLAGS) -lgtest -L../src -lgepserver-lite -lgepclient-lite 55 | 56 | socket_interface_test: LIBS+=-lgmock 57 | 58 | $(TEST_TARGETS_FULL) : \ 59 | test.pb.t.o \ 60 | test_protocol.t.o \ 61 | gep_test_lib.t.o 62 | 63 | $(TEST_TARGETS_LITE) : \ 64 | test_lite.pb.o \ 65 | test_protocol.l.o \ 66 | gep_test_lib.l.o 67 | 68 | # Pattern rules for *_lite targets. 69 | %.l.o: %.cc 70 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DGEP_LITE -c -o $@ $< 71 | 72 | test_lite.pb.o: test_lite.pb.h 73 | 74 | test_lite.pb.o: test_lite.pb.cc 75 | $(CXX) $(CPPFLAGS) $(CXXFLAGS) -DGEP_LITE -c -o $@ $< 76 | 77 | %_test_lite: %_test.l.o 78 | $(CXX) $(TEST_LDFLAGS) -o $@ -Wl,--start-group \ 79 | $(filter-out $*.o,$^) -Wl,--end-group $(TEST_LIBS) 80 | 81 | 82 | install: 83 | 84 | clean:: 85 | rm -f *.pb.* .protos_done 86 | -------------------------------------------------------------------------------- /test/gep_channel_array_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "gep_channel_array.h" 4 | 5 | #include // for socklen_t, ssize_t 6 | 7 | #include "gep_channel_array.h" // for GepChannelArray 8 | #include "gep_protocol.h" // for GepProtocol 9 | #include "gep_test_lib.h" // for TestServer, GepTest 10 | #include "gtest/gtest.h" // for EXPECT_EQ, TEST_F 11 | #include "socket_interface.h" // for SocketInterface 12 | 13 | 14 | class FailingSocketInterface : public SocketInterface { 15 | public: 16 | FailingSocketInterface() : 17 | socket_fail_(false), 18 | recv_fail_(false), 19 | bind_fail_(false), 20 | listen_fail_(false), 21 | getport_fail_(false), 22 | setreuseaddr_fail_(false) { 23 | } 24 | virtual ~FailingSocketInterface() {} 25 | virtual int Socket(int domain, int type, int protocol) { 26 | return socket_fail_ ? -1 : 27 | SocketInterface::Socket(domain, type, protocol); 28 | } 29 | virtual ssize_t Recv(int sockfd, void *buf, size_t len, int flags) { 30 | return recv_fail_ ? -1 : 31 | SocketInterface::Recv(sockfd, buf, len, flags); 32 | } 33 | virtual int Bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 34 | return bind_fail_ ? -1 : 35 | SocketInterface::Bind(sockfd, addr, addrlen); 36 | } 37 | virtual int Listen(int sockfd, int backlog) { 38 | return listen_fail_ ? -1 : 39 | SocketInterface::Listen(sockfd, backlog); 40 | } 41 | virtual int GetPort(const char *log_module, int sock, int *port) { 42 | return getport_fail_ ? -1 : 43 | SocketInterface::GetPort(log_module, sock, port); 44 | } 45 | virtual int SetReuseAddr(const char *log_module, int sock) { 46 | return setreuseaddr_fail_ ? -1 : 47 | SocketInterface::SetReuseAddr(log_module, sock); 48 | } 49 | bool socket_fail_; 50 | bool recv_fail_; 51 | bool bind_fail_; 52 | bool listen_fail_; 53 | bool getport_fail_; 54 | bool setreuseaddr_fail_; 55 | }; 56 | 57 | class GepChannelArrayTest : public GepTest { 58 | }; 59 | 60 | TEST_F(GepChannelArrayTest, FailingSendSocket) { 61 | // stop the GepServer so it does not interfere 62 | server_->Stop(); 63 | 64 | // create an independent GepChannelArray 65 | GepChannelArray *gca = server_->GetGepChannelArray(); 66 | FailingSocketInterface failing_socket_interface; 67 | SocketInterface *old_socket_interface = gca->GetSocketInterface(); 68 | gca->SetSocketInterface(&failing_socket_interface); 69 | 70 | // check different failure modes 71 | gca->Stop(); 72 | failing_socket_interface.socket_fail_ = true; 73 | EXPECT_EQ(-1, gca->OpenServerSocket()); 74 | failing_socket_interface.socket_fail_ = false; 75 | 76 | gca->Stop(); 77 | failing_socket_interface.setreuseaddr_fail_ = true; 78 | EXPECT_EQ(-1, gca->OpenServerSocket()); 79 | failing_socket_interface.setreuseaddr_fail_ = false; 80 | 81 | gca->Stop(); 82 | failing_socket_interface.bind_fail_ = true; 83 | EXPECT_EQ(-1, gca->OpenServerSocket()); 84 | failing_socket_interface.bind_fail_ = false; 85 | 86 | gca->Stop(); 87 | failing_socket_interface.listen_fail_ = true; 88 | EXPECT_EQ(-1, gca->OpenServerSocket()); 89 | failing_socket_interface.listen_fail_ = false; 90 | 91 | gca->Stop(); 92 | failing_socket_interface.getport_fail_ = true; 93 | server_->GetProto()->SetPort(0); 94 | EXPECT_EQ(-1, gca->OpenServerSocket()); 95 | failing_socket_interface.getport_fail_ = false; 96 | 97 | // reinstante the socket interface 98 | // ensure the server is up before teardown 99 | server_->Start(); 100 | gca->SetSocketInterface(old_socket_interface); 101 | } 102 | 103 | int main(int argc, char **argv) { 104 | ::testing::InitGoogleTest(&argc, argv); 105 | return RUN_ALL_TESTS(); 106 | } 107 | -------------------------------------------------------------------------------- /test/gep_channel_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "gep_channel.h" // for GepChannel 4 | 5 | #include // for int64_t, uint8_t 6 | #include // for ssize_t 7 | 8 | #include "gep_channel_array.h" // for GepChannelArray 9 | #include "gep_client.h" // for GepClient 10 | #include "gep_protocol.h" // for GepProtocol, etc 11 | #include "gep_test_lib.h" // for TestServer, GepTest 12 | #include "gtest/gtest.h" // for EXPECT_EQ, TEST_F, etc 13 | #include "socket_interface.h" // for SocketInterface 14 | #include "utils.h" // for gep_log_set_level, etc 15 | 16 | 17 | class FailingSocketInterface: public SocketInterface { 18 | public: 19 | virtual int Socket(int domain, int type, int protocol) { return -1; } 20 | virtual ssize_t Recv(int sockfd, void *buf, size_t len, int flags) { 21 | return -2; 22 | } 23 | int send_error_code_; 24 | virtual int FullSend(int fd, const uint8_t* buf, int size, 25 | int64_t timeout_ms) { 26 | return send_error_code_; 27 | } 28 | }; 29 | 30 | class FlakySocketInterface: public SocketInterface { 31 | public: 32 | FlakySocketInterface(int counter) : counter_(counter) {}; 33 | virtual int Socket(int domain, int type, int protocol) { return -1; } 34 | virtual ssize_t Recv(int sockfd, void *buf, size_t len, int flags) { 35 | return -2; 36 | } 37 | int counter_; 38 | virtual int FullSend(int fd, const uint8_t* buf, int size, 39 | int64_t timeout_ms) { 40 | if ((counter_++ % 2) == 0) 41 | return SocketInterface::FullSend(fd, buf, size, timeout_ms); 42 | else 43 | return 0; 44 | } 45 | }; 46 | 47 | class GepChannelTest : public GepTest { 48 | }; 49 | 50 | TEST_F(GepChannelTest, SetSocket) { 51 | // increase the log verbosity 52 | gep_log_set_level(LOG_DEBUG); 53 | 54 | // push message in the client 55 | GepChannel *gc = client_->GetGepChannel(); 56 | int socket = gc->GetSocket(); 57 | gc->SetSocket(socket); 58 | gc->SendMessage(command1_); 59 | 60 | // push message in the server 61 | GepChannelArray *gca = server_->GetGepChannelArray(); 62 | gca->SendMessage(command3_); 63 | 64 | WaitForSync(2); 65 | } 66 | 67 | TEST_F(GepChannelTest, RecvDataInvalidSocket) { 68 | // set an invalid socket 69 | GepChannel *gc = client_->GetGepChannel(); 70 | gc->SetSocket(-1); 71 | EXPECT_EQ(-1, gc->RecvData()); 72 | } 73 | 74 | TEST_F(GepChannelTest, RecvDataBufferFull) { 75 | // add too much read data 76 | GepChannel *gc = client_->GetGepChannel(); 77 | gc->SetLen(GepProtocol::kMaxMsgLen + 1); 78 | EXPECT_EQ(-1, gc->RecvData()); 79 | } 80 | 81 | TEST_F(GepChannelTest, FailingRecvSocket) { 82 | // set a failing socket interface 83 | GepChannel *gc = client_->GetGepChannel(); 84 | FailingSocketInterface failing_socket_interface{}; 85 | SocketInterface *old_socket_interface = gc->GetSocketInterface(); 86 | gc->SetSocketInterface(&failing_socket_interface); 87 | 88 | // check socket() 89 | EXPECT_EQ(-1, gc->OpenClientSocket()); 90 | 91 | // check recv() 92 | EXPECT_EQ(-1, gc->RecvData()); 93 | 94 | // reinstante the socket interface 95 | gc->SetSocketInterface(old_socket_interface); 96 | } 97 | 98 | TEST_F(GepChannelTest, FailingSendSocket) { 99 | // set a failing socket interface 100 | GepChannel *gc = client_->GetGepChannel(); 101 | FailingSocketInterface failing_socket_interface{}; 102 | SocketInterface *old_socket_interface = gc->GetSocketInterface(); 103 | gc->SetSocketInterface(&failing_socket_interface); 104 | 105 | // check different failure modes 106 | failing_socket_interface.send_error_code_ = 0; 107 | EXPECT_EQ(-1, gc->SendMessage(command1_)); 108 | 109 | failing_socket_interface.send_error_code_ = -1; 110 | EXPECT_EQ(-1, gc->SendMessage(command1_)); 111 | 112 | failing_socket_interface.send_error_code_ = -2; 113 | EXPECT_EQ(-1, gc->SendMessage(command1_)); 114 | 115 | // reinstante the socket interface 116 | gc->SetSocketInterface(old_socket_interface); 117 | } 118 | 119 | TEST_F(GepChannelTest, FlakySendSocket) { 120 | // set a flaky socket interface 121 | GepChannel *gc = client_->GetGepChannel(); 122 | FlakySocketInterface flaky_socket_interface{0}; 123 | SocketInterface *old_socket_interface = gc->GetSocketInterface(); 124 | gc->SetSocketInterface(&flaky_socket_interface); 125 | 126 | // check failure mode 127 | EXPECT_EQ(-1, gc->SendMessage(command1_)); 128 | 129 | // reinstante the socket interface 130 | gc->SetSocketInterface(old_socket_interface); 131 | } 132 | 133 | int main(int argc, char **argv) { 134 | ::testing::InitGoogleTest(&argc, argv); 135 | return RUN_ALL_TESTS(); 136 | } 137 | -------------------------------------------------------------------------------- /test/gep_client_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "gep_client.h" 4 | 5 | #include // for memcpy 6 | #include // for write 7 | #include // for string 8 | 9 | #include "gep_channel.h" // for GepChannel 10 | #include "gep_channel_array.h" // for GepChannelArray 11 | #include "gep_client.h" // for GepClient 12 | #include "gep_test_lib.h" // for TestServer, etc 13 | #include "gtest/gtest.h" // for EXPECT_EQ, ASSERT_TRUE, etc 14 | #include "utils.h" // for SET_UINT32 15 | 16 | 17 | class GepClientTest : public GepTest { 18 | }; 19 | 20 | TEST_F(GepClientTest, ClientReconnect) { 21 | GepChannel *gc = client_->GetGepChannel(); 22 | // initially we are connected 23 | EXPECT_NE(-1, gc->GetSocket()); 24 | // stop the server 25 | server_->Stop(); 26 | // check that the client channel becomes disconnected 27 | ASSERT_TRUE(WaitForTrue([=]() {return gc->GetSocket() == -1;})); 28 | // restart the server 29 | server_->Start(); 30 | // check that the client reconnected 31 | ASSERT_TRUE(WaitForTrue([=]() {return gc->GetSocket() != -1;})); 32 | } 33 | 34 | TEST_F(GepClientTest, ClientReconnectOnGarbageData) { 35 | GepChannel *gc = client_->GetGepChannel(); 36 | // initially we are connected 37 | EXPECT_NE(-1, gc->GetSocket()); 38 | EXPECT_EQ(0, client_->GetReconnectCount()); 39 | // get server socket so we can send the invalid data 40 | int server_socket = server_->GetGepChannelArray()->GetVectorSocket(0); 41 | // send an invalid message 42 | int size_msg = sizeof(kInvalidMessage) - 1; // do not send the '\0' 43 | EXPECT_EQ(size_msg, write(server_socket, kInvalidMessage, size_msg)); 44 | // check that the client reconnected 45 | ASSERT_TRUE(WaitForTrue([=]() {return client_->GetReconnectCount() >= 1;})); 46 | EXPECT_NE(-1, gc->GetSocket()); 47 | } 48 | 49 | TEST_F(GepClientTest, ClientReconnectOnHugeMessageData) { 50 | GepChannel *gc = client_->GetGepChannel(); 51 | // initially we are connected 52 | EXPECT_NE(-1, gc->GetSocket()); 53 | EXPECT_EQ(0, client_->GetReconnectCount()); 54 | // get server socket so we can send the invalid data 55 | int server_socket = server_->GetGepChannelArray()->GetVectorSocket(0); 56 | // send a huge message 57 | int size_msg = sizeof(kHugeInvalidMessage) - 1; // do not send the '\0' 58 | EXPECT_EQ(size_msg, write(server_socket, kHugeInvalidMessage, size_msg)); 59 | // check that the client reconnected 60 | ASSERT_TRUE(WaitForTrue([=]() {return client_->GetReconnectCount() >= 1;})); 61 | EXPECT_NE(-1, gc->GetSocket()); 62 | // ensure the server is seeing the client 63 | ASSERT_TRUE(WaitForTrue([=]() {return server_->GetNumClients() == 1;})); 64 | // push message in the server 65 | server_->Send(command3_); 66 | 67 | WaitForSync(1); 68 | } 69 | 70 | TEST_F(GepClientTest, ClientRestart) { 71 | GepChannel *gc = client_->GetGepChannel(); 72 | // initially we are connected 73 | ASSERT_NE(-1, gc->GetSocket()); 74 | for (int i = 0; i < 20; ++i) { 75 | // stop the client 76 | client_->Stop(); 77 | // ensure the client is stopped 78 | ASSERT_FALSE(client_->GetThreadCtrl()); 79 | // restart the client 80 | ASSERT_EQ(0, client_->Start()); 81 | // ensure the server has seen the client 82 | ASSERT_TRUE(WaitForTrue([=]() {return server_->GetNumClients() != 0;})); 83 | // check that the client reconnected 84 | ASSERT_TRUE(WaitForTrue([=]() {return gc->GetSocket() != -1;})); 85 | } 86 | } 87 | 88 | TEST_F(GepClientTest, ClientDropUnsupportedMessage) { 89 | GepChannel *gc = client_->GetGepChannel(); 90 | // initially we are connected 91 | EXPECT_NE(-1, gc->GetSocket()); 92 | EXPECT_EQ(0, client_->GetReconnectCount()); 93 | // get server socket so we can send the unknown message 94 | int server_socket = server_->GetGepChannelArray()->GetVectorSocket(0); 95 | // send an unknown message 96 | int size_msg = sizeof(kUnsupportedMessage) - 1; // do not send the '\0' 97 | EXPECT_EQ(size_msg, write(server_socket, kUnsupportedMessage, size_msg)); 98 | // push a message in the client 99 | gc->SendMessage(command1_); 100 | // push another message in the server 101 | server_->Send(command3_); 102 | // ensure we did not reconnect 103 | EXPECT_EQ(0, client_->GetReconnectCount()); 104 | WaitForSync(2); 105 | } 106 | 107 | TEST_F(GepClientTest, ClientDropUnsupportedMagicMessage) { 108 | GepChannel *gc = client_->GetGepChannel(); 109 | // initially we are connected 110 | EXPECT_NE(-1, gc->GetSocket()); 111 | EXPECT_EQ(0, client_->GetReconnectCount()); 112 | // get server socket so we can send the unknown message 113 | int server_socket = server_->GetGepChannelArray()->GetVectorSocket(0); 114 | // send a message with an unsupported magic 115 | int size_msg = sizeof(kInvalidMagic) - 1; // do not send the '\0' 116 | EXPECT_EQ(size_msg, write(server_socket, kInvalidMagic, size_msg)); 117 | // ensure we reconnect (invalid magic IDs cause the connection to reset) 118 | ASSERT_TRUE(WaitForTrue([=]() {return client_->GetReconnectCount() == 1;})); 119 | // push a message in the client 120 | gc->SendMessage(command1_); 121 | // push another message in the server 122 | server_->Send(command3_); 123 | WaitForSync(1); 124 | } 125 | 126 | TEST_F(GepClientTest, ClientSupportsFragmentation) { 127 | GepChannel *gc = client_->GetGepChannel(); 128 | // initially we are connected 129 | EXPECT_NE(-1, gc->GetSocket()); 130 | EXPECT_EQ(0, client_->GetReconnectCount()); 131 | // get server socket so we can send the unknown message 132 | int server_socket = server_->GetGepChannelArray()->GetVectorSocket(0); 133 | // push several valid messages in the client 134 | char kSeveralMessages[1024]; 135 | int bi = 0; 136 | // start with an unsupported message 137 | int size_msg = sizeof(kUnsupportedMessage) - 1; // do not send the '\0' 138 | memcpy(kSeveralMessages + bi, kUnsupportedMessage, size_msg); 139 | bi += size_msg; 140 | // then add several supported messages 141 | int total = 10; 142 | int hdr_size_msg = sizeof(kRawCommand4Header) - 1; // do not send '\0' 143 | int body_size_msg = command4_str_.length(); 144 | for (int i = 0; i < total; ++i) { 145 | memcpy(kSeveralMessages + bi, kRawCommand4Header, hdr_size_msg); 146 | bi += hdr_size_msg; 147 | SET_UINT32(kSeveralMessages + bi, body_size_msg); 148 | bi += 4; 149 | memcpy(kSeveralMessages + bi, command4_str_.c_str(), body_size_msg); 150 | bi += body_size_msg; 151 | } 152 | EXPECT_EQ(bi, write(server_socket, kSeveralMessages, bi)); 153 | 154 | // push another message in the client 155 | gc->SendMessage(command1_); 156 | 157 | // push message in the server 158 | server_->Send(command3_); 159 | 160 | WaitForSync(total + 2); 161 | } 162 | 163 | int main(int argc, char **argv) { 164 | ::testing::InitGoogleTest(&argc, argv); 165 | return RUN_ALL_TESTS(); 166 | } 167 | -------------------------------------------------------------------------------- /test/gep_end_to_end_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "gtest/gtest.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include // for NULL 14 | #include // for string 15 | #include // for usleep 16 | 17 | #include "../src/utils.h" 18 | #include "gep_test_lib.h" 19 | #ifndef GEP_LITE 20 | #include "test.pb.h" 21 | #else 22 | #include "test_lite.pb.h" 23 | #endif 24 | #include "test_protocol.h" 25 | 26 | using namespace libgep_utils; 27 | 28 | const int kNumWriters = 20; 29 | 30 | class GepEndToEndTest : public GepTest { 31 | public: 32 | void SenderPusherThread(); 33 | void SenderLockThread(); 34 | }; 35 | 36 | TEST_F(GepEndToEndTest, BasicEndToEnd) { 37 | // push message in the client 38 | client_->Send(command1_); 39 | 40 | // push message in the server 41 | server_->Send(command3_); 42 | 43 | WaitForSync(2); 44 | } 45 | 46 | TEST_F(GepEndToEndTest, ExplicitEndToEnd) { 47 | // push message in the server to an explicit client 48 | int id = server_->GetGepChannelArray()->GetClientId(0); 49 | server_->Send(command3_, id); 50 | 51 | WaitForSync(1); 52 | } 53 | 54 | TEST_F(GepEndToEndTest, CallbackFailure) { 55 | // push message in the client 56 | client_->Send(command2_); 57 | 58 | WaitForSync(1); 59 | } 60 | 61 | void GepEndToEndTest::SenderPusherThread() { 62 | // wait for the ready signal 63 | while (!ready_) 64 | std::this_thread::yield(); 65 | 66 | // push message in the server 67 | server_->Send(command3_); 68 | } 69 | 70 | TEST_F(GepEndToEndTest, ParallelEndToEnd) { 71 | // push message in the client 72 | client_->Send(command1_); 73 | 74 | // start the sender pusher threads 75 | ready_ = false; 76 | std::vector threads; 77 | for (int i = 0; i < kNumWriters; ++i) 78 | threads.push_back(std::thread(&GepEndToEndTest::SenderPusherThread, this)); 79 | ready_ = true; 80 | 81 | // wait for all the sender pushers to finish 82 | for (auto &th : threads) 83 | th.join(); 84 | 85 | WaitForSync(1 + kNumWriters); 86 | EXPECT_EQ(0, client_->GetReconnectCount()); 87 | } 88 | 89 | TEST_F(GepEndToEndTest, EndToEndDifferentMagic) { 90 | // use a different magic number 91 | uint32_t new_magic = MakeTag('r', 'f', 'l', 'a'); 92 | cproto_->SetMagic(new_magic); 93 | sproto_->SetMagic(new_magic); 94 | 95 | // push message in the client 96 | client_->Send(command1_); 97 | 98 | // push message in the server 99 | server_->Send(command3_); 100 | 101 | // ensure we did not reconnect 102 | EXPECT_EQ(0, client_->GetReconnectCount()); 103 | WaitForSync(2); 104 | } 105 | 106 | TEST_F(GepEndToEndTest, MultipleMessagesAreAllReceived) { 107 | // push 2x messages in the client 108 | client_->Send(command1_); 109 | client_->Send(command1_); 110 | 111 | // push message in the server 112 | server_->Send(command3_); 113 | 114 | // note that WaitForSync() will wait for 3 messages or fail 115 | WaitForSync(3); 116 | } 117 | 118 | TEST_F(GepEndToEndTest, CallbackDeadlock) { 119 | // push message in the client 120 | client_->Send(control_message_ping_); 121 | 122 | WaitForSync(2); 123 | } 124 | 125 | void GepEndToEndTest::SenderLockThread() { 126 | // ensure the sender locks the non-GEP mutex 127 | std::lock_guard lock(non_gep_lock_); 128 | 129 | // wait for the server thread to get into the callback 130 | while (!stage1_) 131 | std::this_thread::yield(); 132 | 133 | // let the server thread proceed 134 | stage2_ = true; 135 | 136 | // push any message in the server 137 | server_->Send(command3_); 138 | } 139 | 140 | TEST_F(GepEndToEndTest, CallbackCrossed) { 141 | // init sync mechanism 142 | stage1_ = false; 143 | stage2_ = false; 144 | 145 | // push message in the client 146 | client_->Send(control_message_get_lock_); 147 | 148 | // start the sender thread 149 | auto th = std::thread(&GepEndToEndTest::SenderLockThread, this); 150 | 151 | // wait for all the sender thread to finish 152 | if (WaitForSync(1)) 153 | th.join(); 154 | } 155 | 156 | TEST_F(GepEndToEndTest, EndToEndBinaryProtocol) { 157 | // use binary mode 158 | cproto_->SetMode(GepProtocol::MODE_BINARY); 159 | sproto_->SetMode(GepProtocol::MODE_BINARY); 160 | 161 | // push message in the client 162 | client_->Send(command1_); 163 | 164 | // push message in the server 165 | server_->Send(command3_); 166 | 167 | // ensure we did not reconnect 168 | EXPECT_EQ(0, client_->GetReconnectCount()); 169 | WaitForSync(2); 170 | } 171 | 172 | int main(int argc, char **argv) { 173 | ::testing::InitGoogleTest(&argc, argv); 174 | return RUN_ALL_TESTS(); 175 | } 176 | -------------------------------------------------------------------------------- /test/gep_protocol_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "gep_protocol.h" 4 | 5 | #include // for string 6 | 7 | #include "gep_test_lib.h" // for GepTest 8 | #include "gtest/gtest-message.h" // for Message 9 | #include "gtest/gtest.h" // for EXPECT_TRUE, InitGoogleTest, etc 10 | #include "test_protocol.h" // for TestProtocol 11 | #include "utils.h" // for ProtobufEqual, etc 12 | 13 | 14 | class GepProtocolTest : public GepTest { 15 | public: 16 | void SetUp(); 17 | void TearDown() {}; 18 | 19 | TestProtocol *proto_; 20 | }; 21 | 22 | void GepProtocolTest::SetUp() { 23 | InitData(); 24 | // create a Test protocol instance 25 | proto_ = new TestProtocol(0); 26 | proto_->SetSelectTimeoutUsec(msecs_to_usecs(10)); 27 | } 28 | 29 | TEST_F(GepProtocolTest, Serialize) { 30 | // use the client GepChannel to test Serialize 31 | static Command1 empty_command1; 32 | struct gep_serialize_test { 33 | int line; 34 | Command1 *command1; 35 | std::string expected_command1_str; 36 | } test_arr[] = { 37 | {__LINE__, &command1_, command1_str_}, 38 | {__LINE__, &empty_command1, ""}, 39 | }; 40 | 41 | for (const auto &test_item : test_arr) { 42 | std::string capture_str; 43 | EXPECT_TRUE(proto_->Serialize(*test_item.command1, &capture_str)) << 44 | "Error on line " << test_item.line; 45 | EXPECT_STREQ(test_item.expected_command1_str.c_str(), 46 | capture_str.c_str()) << 47 | "Error on line " << test_item.line; 48 | } 49 | } 50 | 51 | TEST_F(GepProtocolTest, Unserialize) { 52 | // use the client GepChannel to test Unserialize 53 | static Command1 empty_command1; 54 | struct gep_unserialize_test { 55 | int line; 56 | bool success; 57 | Command1 *expected_command1; 58 | std::string command1_str; 59 | } test_arr[] = { 60 | {__LINE__, true, &command1_, command1_str_}, 61 | {__LINE__, true, &empty_command1, ""}, 62 | // invalid message 63 | {__LINE__, false, &empty_command1, "invalid text protobuf"}, 64 | }; 65 | 66 | for (const auto &test_item : test_arr) { 67 | Command1 msg; 68 | EXPECT_EQ(test_item.success, 69 | proto_->Unserialize(test_item.command1_str, &msg)) << 70 | "Error on line " << test_item.line; 71 | if (test_item.success) 72 | EXPECT_TRUE(ProtobufEqual(*test_item.expected_command1, msg)) << 73 | "Error on line " << test_item.line; 74 | } 75 | } 76 | 77 | int main(int argc, char **argv) { 78 | ::testing::InitGoogleTest(&argc, argv); 79 | return RUN_ALL_TESTS(); 80 | } 81 | -------------------------------------------------------------------------------- /test/gep_server_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include // for remove 4 | #include // for memset 5 | #include // for usleep 6 | 7 | #include "gep_server.h" 8 | #include "gep_test_lib.h" 9 | #include "test_protocol.h" 10 | 11 | #include "gtest/gtest.h" 12 | 13 | class GepServerTest : public GepTest { 14 | }; 15 | 16 | TEST_F(GepServerTest, ServerReconnect) { 17 | GepChannel *gc = client_->GetGepChannel(); 18 | // initially we are connected 19 | EXPECT_NE(-1, gc->GetSocket()); 20 | // stop the server 21 | server_->Stop(); 22 | // check that the client channel becomes disconnected 23 | ASSERT_TRUE(WaitForTrue([=]() {return gc->GetSocket() == -1;})); 24 | // restart the server 25 | server_->Start(); 26 | // check that the client reconnected 27 | ASSERT_TRUE(WaitForTrue([=]() {return gc->GetSocket() != -1;})); 28 | } 29 | 30 | TEST_F(GepServerTest, ServerDropUnsupportedMessage) { 31 | GepChannel *gc = client_->GetGepChannel(); 32 | // initially we are connected 33 | EXPECT_NE(-1, gc->GetSocket()); 34 | EXPECT_EQ(0, client_->GetReconnectCount()); 35 | // get client socket so we can send the unknown message 36 | int client_socket = gc->GetSocket(); 37 | // send an unknown message 38 | int size_msg = sizeof(kUnsupportedMessage) - 1; // do not send the '\0' 39 | EXPECT_EQ(size_msg, write(client_socket, kUnsupportedMessage, size_msg)); 40 | // push another message in the client 41 | gc->SendMessage(command1_); 42 | // push a message in the server 43 | GepChannelArray *gca = server_->GetGepChannelArray(); 44 | gca->SendMessage(command3_); 45 | // ensure we did not reconnect 46 | EXPECT_EQ(0, client_->GetReconnectCount()); 47 | WaitForSync(2); 48 | } 49 | 50 | int main(int argc, char **argv) { 51 | ::testing::InitGoogleTest(&argc, argv); 52 | return RUN_ALL_TESTS(); 53 | } 54 | -------------------------------------------------------------------------------- /test/gep_test_lib.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "gep_test_lib.h" 4 | #include "gtest/gtest.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include // for NULL 15 | #include // for string 16 | #include // for usleep 17 | 18 | #include "../src/utils.h" 19 | #ifndef GEP_LITE 20 | #include "test.pb.h" 21 | #else 22 | #include "test_lite.pb.h" 23 | #endif 24 | #include "test_protocol.h" 25 | 26 | using namespace libgep_utils; 27 | 28 | 29 | const int kMaxChannels = 8; 30 | 31 | int64_t GepTest::kWaitTimeoutUsecs = secs_to_usecs(6); 32 | void* GepTest::context_ = nullptr; 33 | std::atomic GepTest::synced_(0); 34 | std::atomic GepTest::ready_(false); 35 | std::atomic GepTest::stage1_(false); 36 | std::atomic GepTest::stage2_(false); 37 | 38 | void GepTest::DoSync() { 39 | synced_++; 40 | } 41 | 42 | bool GepTest::Recv(const Command1 &msg, int id) { 43 | // check the msg received in the client 44 | EXPECT_TRUE(ProtobufEqual(rcommand1_, msg)); 45 | DoSync(); 46 | return true; 47 | } 48 | 49 | bool GepTest::Recv(const Command2 &msg, int id) { 50 | // check the msg received in the client 51 | EXPECT_TRUE(ProtobufEqual(rcommand2_, msg)); 52 | DoSync(); 53 | return false; 54 | } 55 | 56 | bool GepTest::Recv(const Command3 &msg, int id) { 57 | // check the msg received in the client 58 | EXPECT_TRUE(ProtobufEqual(rcommand3_, msg)); 59 | DoSync(); 60 | return true; 61 | } 62 | 63 | bool GepTest::Recv(const Command4 &msg, int id) { 64 | // check the msg received in the client 65 | EXPECT_TRUE(ProtobufEqual(rcommand4_, msg)); 66 | DoSync(); 67 | return true; 68 | } 69 | 70 | bool GepTest::Recv(const ControlMessage &msg, int id) { 71 | // check the msg received in the server 72 | if (ProtobufEqual(rcontrol_message_ping_, msg)) { 73 | // send pong message 74 | GepChannelArray *gca = server_->GetGepChannelArray(); 75 | gca->SendMessage(rcontrol_message_pong_); 76 | } else if (ProtobufEqual(rcontrol_message_get_lock_, msg)) { 77 | // let the main thread know the server thread is in the callback 78 | stage1_ = true; 79 | // wait until the server thread allows us to proceed 80 | while (!stage2_) 81 | std::this_thread::yield(); 82 | // get the non-GEP mutex 83 | non_gep_lock_.lock(); 84 | // release the non-GEP mutex 85 | non_gep_lock_.unlock(); 86 | } 87 | DoSync(); 88 | return true; 89 | } 90 | 91 | bool GepTest::WaitForTrue(std::function fun) { 92 | int64_t max_time = GetUnixTimeUsec() + kWaitTimeoutUsecs; 93 | bool result = fun(); 94 | while (!result && GetUnixTimeUsec() < max_time) { 95 | usleep(1000); 96 | result = fun(); 97 | } 98 | return result; 99 | } 100 | 101 | bool GepTest::WaitForSync(int number) { 102 | return WaitForTrue([=]() {return GetSynced() >= number;}); 103 | } 104 | 105 | void GepTest::SetUpTestCase() { 106 | } 107 | 108 | void GepTest::TearDownTestCase() { 109 | // clean up protobuf library 110 | google::protobuf::ShutdownProtobufLibrary(); 111 | } 112 | 113 | void GepTest::InitData() { 114 | // create some messages 115 | command1_.set_a(0xaaaaaaaaaaaaaaaa); 116 | command1_.set_b(0xbbbbbbbb); 117 | command3_.set_id(123456789); 118 | command4_.set_id(123456789); 119 | control_message_ping_.set_command(ControlMessage::COMMAND_PING); 120 | control_message_pong_.set_command(ControlMessage::COMMAND_PONG); 121 | control_message_get_lock_.set_command(ControlMessage::COMMAND_GET_LOCK); 122 | 123 | // copy them 124 | rcommand1_ = command1_; 125 | rcommand2_ = command2_; 126 | rcommand3_ = command3_; 127 | rcommand4_ = command4_; 128 | rcontrol_message_ping_ = control_message_ping_; 129 | rcontrol_message_pong_ = control_message_pong_; 130 | rcontrol_message_get_lock_ = control_message_get_lock_; 131 | 132 | // add text version 133 | TestProtocol proto(0); 134 | EXPECT_TRUE(proto.Serialize(command1_, &command1_str_)); 135 | EXPECT_TRUE(proto.Serialize(command3_, &command3_str_)); 136 | EXPECT_TRUE(proto.Serialize(command4_, &command4_str_)); 137 | EXPECT_TRUE(proto.Serialize(control_message_ping_, 138 | &control_message_ping_str_)); 139 | EXPECT_TRUE(proto.Serialize(control_message_pong_, 140 | &control_message_pong_str_)); 141 | EXPECT_TRUE(proto.Serialize(control_message_get_lock_, 142 | &control_message_get_lock_str_)); 143 | } 144 | 145 | void GepTest::SetUp() { 146 | InitData(); 147 | 148 | // create Test protocol instances 149 | cproto_ = new TestProtocol(0); 150 | sproto_ = new TestProtocol(0); 151 | 152 | // ensure the protocol objects answer fast 153 | cproto_->SetSelectTimeoutUsec(msecs_to_usecs(10)); 154 | sproto_->SetSelectTimeoutUsec(msecs_to_usecs(10)); 155 | 156 | context_ = reinterpret_cast(this); 157 | 158 | // create GEP server 159 | server_ = new TestServer("gep_test_server", kMaxChannels, context_, sproto_, 160 | &kGepTestOps); 161 | ASSERT_TRUE(server_); 162 | 163 | // create GEP client 164 | client_ = new GepClient("gep_test_client", context_, cproto_, &kGepTestOps); 165 | ASSERT_TRUE(client_); 166 | 167 | // reset sync flag 168 | synced_ = 0; 169 | 170 | // start server in a random port 171 | server_->GetProto()->SetPort(0); 172 | EXPECT_EQ(0, server_->Start()); 173 | int port = server_->GetProto()->GetPort(); 174 | EXPECT_GT(port, 0); 175 | 176 | // start client 177 | client_->GetProto()->SetPort(port); 178 | EXPECT_EQ(0, client_->Start()); 179 | 180 | // ensure the server has seen the client 181 | ASSERT_TRUE(WaitForTrue([=]() {return server_->GetNumClients() != 0;})); 182 | } 183 | 184 | void GepTest::TearDown() { 185 | // stop client and server 186 | client_->Stop(); 187 | server_->Stop(); 188 | // ensure all clients have been deleted 189 | EXPECT_TRUE(server_->ids_.empty()); 190 | // delete them 191 | delete client_; 192 | delete server_; 193 | } 194 | -------------------------------------------------------------------------------- /test/gep_test_lib.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _TEST_GEP_TEST_LIB_H_ 4 | #define _TEST_GEP_TEST_LIB_H_ 5 | 6 | #include "gtest/gtest.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include // for NULL 17 | #include // for string 18 | #include // for usleep 19 | 20 | #include "../src/utils.h" 21 | #ifndef GEP_LITE 22 | #include "test.pb.h" 23 | #else 24 | #include "test_lite.pb.h" 25 | #endif 26 | #include "test_protocol.h" 27 | 28 | using namespace libgep_utils; 29 | 30 | const char kRawCommand4Header[] = "geppcmd4"; 31 | const char kInvalidMessage[] = "geppcmd3\000\000\000\001x"; 32 | const char kHugeInvalidMessage[] = "geppcmd3\377\377\377\377yy"; 33 | const char kUnsupportedMessage[] = "geppxyza\000\000\000\001x"; 34 | const char kInvalidMagic[] = "abcdcmd4\000\000\000\015id: 123456789"; 35 | 36 | 37 | class TestServer : public GepServer { 38 | public: 39 | TestServer(const std::string &name, int max_channels, void *context, 40 | GepProtocol *proto, const GepVFT* ops) 41 | : GepServer(name, max_channels, context, proto, ops) { 42 | } 43 | 44 | virtual int Start() { 45 | ids_.clear(); 46 | return GepServer::Start(); 47 | } 48 | 49 | std::vector ids_; 50 | 51 | virtual void AddClient(int id) { 52 | ids_.push_back(id); 53 | } 54 | virtual void DelClient(int id) { 55 | auto it = find(ids_.begin(), ids_.end(), id); 56 | // ensure the client exists 57 | EXPECT_NE(ids_.end(), it); 58 | // remove it 59 | ids_.erase(it); 60 | } 61 | }; 62 | 63 | class GepTest : public ::testing::Test { 64 | public: 65 | static void DoSync(); 66 | void SenderPusherThread(); 67 | void SenderLockThread(); 68 | 69 | // protocol object callbacks: These are object (non-static) callback 70 | // methods, which is handy for the callers. 71 | virtual bool Recv(const Command1 &msg, int id); 72 | virtual bool Recv(const Command2 &msg, int id); 73 | virtual bool Recv(const Command3 &msg, int id); 74 | virtual bool Recv(const Command4 &msg, int id); 75 | virtual bool Recv(const ControlMessage &msg, int id); 76 | 77 | // maximum wait for any busy loop 78 | static int64_t kWaitTimeoutUsecs; 79 | 80 | // messages for testing (add copy for receiver to avoid race conditions 81 | // on accessing to the same message) 82 | Command1 command1_, rcommand1_; 83 | Command2 command2_, rcommand2_; 84 | Command3 command3_, rcommand3_; 85 | Command4 command4_, rcommand4_; 86 | ControlMessage control_message_ping_, rcontrol_message_ping_; 87 | ControlMessage control_message_pong_, rcontrol_message_pong_; 88 | ControlMessage control_message_get_lock_, rcontrol_message_get_lock_; 89 | 90 | // text version 91 | std::string command1_str_; 92 | std::string command2_str_; 93 | std::string command3_str_; 94 | std::string command4_str_; 95 | std::string control_message_ping_str_; 96 | std::string control_message_pong_str_; 97 | std::string control_message_get_lock_str_; 98 | 99 | void InitData(); 100 | 101 | protected: 102 | static void SetUpTestCase(); 103 | static void TearDownTestCase(); 104 | 105 | void SetUp(); 106 | void TearDown(); 107 | static bool WaitForSync(int number); 108 | static bool WaitForTrue(std::function fun); 109 | 110 | TestProtocol *cproto_; 111 | TestProtocol *sproto_; 112 | 113 | TestServer *server_; 114 | GepClient *client_; 115 | static void *context_; 116 | 117 | // ready flag 118 | static std::atomic ready_; 119 | 120 | // other sync flags 121 | static std::atomic stage1_; 122 | static std::atomic stage2_; 123 | 124 | // lock for the CallbackCrossed test 125 | std::mutex non_gep_lock_; 126 | 127 | private: 128 | static std::atomic synced_; 129 | static int GetSynced() { return synced_; } 130 | }; 131 | 132 | const GepVFT kGepTestOps = { 133 | {TestProtocol::MSG_TAG_COMMAND_1, &RecvMessageId}, 134 | {TestProtocol::MSG_TAG_COMMAND_2, &RecvMessageId}, 135 | {TestProtocol::MSG_TAG_COMMAND_3, &RecvMessageId}, 136 | {TestProtocol::MSG_TAG_COMMAND_4, &RecvMessageId}, 137 | {TestProtocol::MSG_TAG_CONTROL, &RecvMessageId}, 138 | }; 139 | 140 | #endif // _TEST_GEP_TEST_LIB_H_ 141 | -------------------------------------------------------------------------------- /test/mock_raw_socket_interface.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _TEST_MOCK_RAW_SOCKET_INTERFACE_H_ 4 | #define _TEST_MOCK_RAW_SOCKET_INTERFACE_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | #include // for int64_t 10 | #include 11 | #include 12 | 13 | #include "raw_socket_interface.h" 14 | 15 | 16 | // Mock version of RawSocketInterface for testing. 17 | class MockRawSocketInterface : public RawSocketInterface { 18 | public: 19 | MockRawSocketInterface() : RawSocketInterface() {} 20 | ~MockRawSocketInterface() override {} 21 | 22 | MOCK_METHOD4(Send, ssize_t(int sockfd, const void *buf, size_t len, 23 | int flags)); 24 | }; 25 | 26 | #endif // _TEST_MOCK_RAW_SOCKET_INTERFACE_H_ 27 | -------------------------------------------------------------------------------- /test/mock_time_manager.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #ifndef _TEST_MOCK_TIME_MANAGER_H_ 4 | #define _TEST_MOCK_TIME_MANAGER_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | #include // for uint64_t 10 | 11 | #include "time_manager.h" 12 | 13 | 14 | // Mock version of TimeManager for testing. 15 | class MockTimeManager : public TimeManager { 16 | public: 17 | MockTimeManager() : TimeManager() {} 18 | ~MockTimeManager() override {} 19 | 20 | MOCK_METHOD1(ms_elapse, uint64_t(uint64_t msecs)); 21 | }; 22 | 23 | #endif // _TEST_MOCK_TIME_MANAGER_H_ 24 | -------------------------------------------------------------------------------- /test/socket_interface_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "socket_interface.h" 4 | 5 | #include 6 | #include // for AssertHelper, TEST_F, etc 7 | #include // for uint8_t, int64_t 8 | 9 | #include "mock_raw_socket_interface.h" // for MockRawSocketInterface 10 | #include "mock_time_manager.h" // for MockTimeManager 11 | 12 | using ::testing::_; 13 | using ::testing::Return; 14 | using ::testing::SetErrnoAndReturn; 15 | 16 | 17 | class TestableSocketInterface : public SocketInterface { 18 | public: 19 | TestableSocketInterface() : SocketInterface() {}; 20 | using SocketInterface::raw_socket_interface_; 21 | using SocketInterface::time_manager_; 22 | }; 23 | 24 | class SocketInterfaceTest : public ::testing::Test { 25 | public: 26 | void SetUp() override { 27 | socket_interface_.reset(new TestableSocketInterface()); 28 | mock_raw_socket_interface_ = new MockRawSocketInterface(); 29 | socket_interface_->raw_socket_interface_.reset(mock_raw_socket_interface_); 30 | mock_time_manager_ = new MockTimeManager(); 31 | socket_interface_->time_manager_.reset(mock_time_manager_); 32 | } 33 | void TearDown() override { 34 | socket_interface_.reset(nullptr); 35 | } 36 | 37 | protected: 38 | std::unique_ptr socket_interface_; 39 | MockRawSocketInterface* mock_raw_socket_interface_; 40 | MockTimeManager* mock_time_manager_; 41 | }; 42 | 43 | 44 | TEST_F(SocketInterfaceTest, FullSendOK) { 45 | EXPECT_CALL(*mock_raw_socket_interface_, Send(_, _, _, _)) 46 | .WillOnce(Return(1024)); 47 | EXPECT_CALL(*mock_time_manager_, ms_elapse(_)) 48 | .WillOnce(Return(1)); 49 | 50 | int fd = 1; 51 | const uint8_t buf[1024] = {}; 52 | int size = 1024; 53 | int64_t timeout_ms = 10; 54 | EXPECT_EQ(1024, socket_interface_->FullSend(fd, buf, size, timeout_ms)); 55 | } 56 | 57 | TEST_F(SocketInterfaceTest, FullSendShutdown) { 58 | EXPECT_CALL(*mock_raw_socket_interface_, Send(_, _, _, _)) 59 | .WillOnce(Return(0)); 60 | EXPECT_CALL(*mock_time_manager_, ms_elapse(_)) 61 | .WillOnce(Return(1)); 62 | 63 | int fd = 1; 64 | const uint8_t buf[1024] = {}; 65 | int size = 1024; 66 | int64_t timeout_ms = 10; 67 | EXPECT_EQ(-2, socket_interface_->FullSend(fd, buf, size, timeout_ms)); 68 | } 69 | 70 | TEST_F(SocketInterfaceTest, FullSendError) { 71 | EXPECT_CALL(*mock_raw_socket_interface_, Send(_, _, _, _)) 72 | .WillOnce(SetErrnoAndReturn(EADDRINUSE, -1)); 73 | EXPECT_CALL(*mock_time_manager_, ms_elapse(_)) 74 | .WillOnce(Return(1)); 75 | 76 | int fd = 1; 77 | const uint8_t buf[1024] = {}; 78 | int size = 1024; 79 | int64_t timeout_ms = 10; 80 | EXPECT_EQ(-1, socket_interface_->FullSend(fd, buf, size, timeout_ms)); 81 | } 82 | 83 | TEST_F(SocketInterfaceTest, FullSendTimeout) { 84 | EXPECT_CALL(*mock_raw_socket_interface_, Send(_, _, _, _)) 85 | .WillOnce(SetErrnoAndReturn(EAGAIN, -1)); 86 | EXPECT_CALL(*mock_time_manager_, ms_elapse(_)) 87 | .WillOnce(Return(1)) 88 | .WillOnce(Return(11)); 89 | 90 | int fd = 1; 91 | const uint8_t buf[1024] = {}; 92 | int size = 1024; 93 | int64_t timeout_ms = 10; 94 | EXPECT_EQ(0, socket_interface_->FullSend(fd, buf, size, timeout_ms)); 95 | } 96 | 97 | TEST_F(SocketInterfaceTest, FullSendTimeoutWhileReceiving) { 98 | EXPECT_CALL(*mock_raw_socket_interface_, Send(_, _, _, _)) 99 | .WillRepeatedly(Return(1)); 100 | EXPECT_CALL(*mock_time_manager_, ms_elapse(_)) 101 | .WillOnce(Return(1)) 102 | .WillOnce(Return(11)); 103 | 104 | int fd = 1; 105 | const uint8_t buf[1024] = {}; 106 | int size = 1024; 107 | int64_t timeout_ms = 10; 108 | EXPECT_EQ(0, socket_interface_->FullSend(fd, buf, size, timeout_ms)); 109 | } 110 | -------------------------------------------------------------------------------- /test/test.proto: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // A protocol buffer representation of an example API. 4 | 5 | syntax = "proto2"; 6 | 7 | 8 | message Command1 { 9 | optional int64 a = 1; 10 | optional int32 b = 2; 11 | } 12 | 13 | message Command2 { 14 | optional int64 id = 1; 15 | } 16 | 17 | message Command3 { 18 | optional int64 id = 1; 19 | } 20 | 21 | message Command4 { 22 | optional int64 id = 1; 23 | } 24 | 25 | message ControlMessage { 26 | enum Command { 27 | COMMAND_PING = 0; 28 | COMMAND_PONG = 1; 29 | COMMAND_GET_LOCK = 2; 30 | } 31 | optional Command command = 1 [default = COMMAND_PING]; 32 | } 33 | -------------------------------------------------------------------------------- /test/test_lite.proto: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // A protocol buffer representation of an example API. 4 | 5 | syntax = "proto2"; 6 | 7 | option optimize_for = LITE_RUNTIME; 8 | 9 | message Command1 { 10 | optional int64 a = 1; 11 | optional int32 b = 2; 12 | } 13 | 14 | message Command2 { 15 | optional int64 id = 1; 16 | } 17 | 18 | message Command3 { 19 | optional int64 id = 1; 20 | } 21 | 22 | message Command4 { 23 | optional int64 id = 1; 24 | } 25 | 26 | message ControlMessage { 27 | enum Command { 28 | COMMAND_PING = 0; 29 | COMMAND_PONG = 1; 30 | COMMAND_GET_LOCK = 2; 31 | } 32 | optional Command command = 1 [default = COMMAND_PING]; 33 | } 34 | -------------------------------------------------------------------------------- /test/test_protocol.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP/Test protocol implementation. 4 | 5 | #ifndef _GNU_SOURCE 6 | #define _GNU_SOURCE 7 | #endif 8 | 9 | #ifdef __cplusplus 10 | #define __STDC_FORMAT_MACROS 11 | #endif 12 | 13 | 14 | #include "test_protocol.h" 15 | 16 | #include // for NULL 17 | 18 | #include "gep_common.h" // for GepProtobufMessage, etc 19 | #ifndef GEP_LITE 20 | #include "test.pb.h" 21 | #else 22 | #include "test_lite.pb.h" 23 | #endif 24 | 25 | 26 | TestProtocol::TestProtocol(int port) 27 | : GepProtocol(port) { 28 | } 29 | 30 | // TODO(chema): we must redefine these constants because the compiler is 31 | // not smart enough to treat TestProtocol::MSG_TAG_* as the corresponding 32 | // literal 33 | // \ref http://stackoverflow.com/questions/22867654/enum-vs-constexpr-for-actual-static-constants-inside-classes 34 | constexpr uint32_t TestProtocol::MSG_TAG_COMMAND_1; 35 | constexpr uint32_t TestProtocol::MSG_TAG_COMMAND_2; 36 | constexpr uint32_t TestProtocol::MSG_TAG_COMMAND_3; 37 | constexpr uint32_t TestProtocol::MSG_TAG_COMMAND_4; 38 | constexpr uint32_t TestProtocol::MSG_TAG_CONTROL; 39 | 40 | uint32_t TestProtocol::GetTag(const GepProtobufMessage *msg) { 41 | // TODO(chema): use send VFT map here instead of listing all the cases (?) 42 | if (dynamic_cast(msg) != NULL) 43 | return MSG_TAG_COMMAND_1; 44 | else if (dynamic_cast(msg) != NULL) 45 | return MSG_TAG_COMMAND_2; 46 | else if (dynamic_cast(msg) != NULL) 47 | return MSG_TAG_COMMAND_3; 48 | else if (dynamic_cast(msg) != NULL) 49 | return MSG_TAG_COMMAND_4; 50 | else if (dynamic_cast(msg) != NULL) 51 | return MSG_TAG_CONTROL; 52 | return 0; 53 | } 54 | 55 | GepProtobufMessage *TestProtocol::GetMessage(uint32_t tag) { 56 | GepProtobufMessage *msg = NULL; 57 | switch (tag) { 58 | case MSG_TAG_COMMAND_1: 59 | msg = new Command1(); 60 | break; 61 | case MSG_TAG_COMMAND_2: 62 | msg = new Command2(); 63 | break; 64 | case MSG_TAG_COMMAND_3: 65 | msg = new Command3(); 66 | break; 67 | case MSG_TAG_COMMAND_4: 68 | msg = new Command4(); 69 | break; 70 | case MSG_TAG_CONTROL: 71 | msg = new ControlMessage(); 72 | break; 73 | } 74 | return msg; 75 | } 76 | -------------------------------------------------------------------------------- /test/test_protocol.h: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | // GEP/Test protocol 4 | // 5 | // Test is a GEP-based protocol. 6 | // 7 | 8 | #ifndef _TEST_PROTOCOL_H_ 9 | #define _TEST_PROTOCOL_H_ 10 | 11 | #include // for uint32_t 12 | 13 | #include "gep_common.h" // for GepProtobufMessage 14 | #include "gep_protocol.h" // for MakeTag, GepProtocol 15 | 16 | // Sagesrv internals protocol 17 | class TestProtocol : public GepProtocol { 18 | public: 19 | explicit TestProtocol(int port = kPort); 20 | virtual ~TestProtocol() {} 21 | 22 | // basic protocol constants 23 | static const int kPort = 6999; 24 | 25 | // supported messages 26 | static constexpr uint32_t MSG_TAG_COMMAND_1 = 27 | MakeTag('c', 'm', 'd', '1'); 28 | static constexpr uint32_t MSG_TAG_COMMAND_2 = 29 | MakeTag('c', 'm', 'd', '2'); 30 | static constexpr uint32_t MSG_TAG_COMMAND_3 = 31 | MakeTag('c', 'm', 'd', '3'); 32 | static constexpr uint32_t MSG_TAG_COMMAND_4 = 33 | MakeTag('c', 'm', 'd', '4'); 34 | static constexpr uint32_t MSG_TAG_CONTROL = 35 | MakeTag('c', 't', 'r', 'l'); 36 | 37 | // returns the tag associated to a message. 38 | virtual uint32_t GetTag(const GepProtobufMessage *msg); 39 | // constructs an object of a given type. 40 | virtual GepProtobufMessage *GetMessage(uint32_t tag); 41 | }; 42 | 43 | #endif // _TEST_PROTOCOL_H_ 44 | -------------------------------------------------------------------------------- /test/utils_test.cc: -------------------------------------------------------------------------------- 1 | // Copyright Google Inc. Apache 2.0. 2 | 3 | #include "utils.h" 4 | 5 | #include // for uint8_t, int64_t 6 | #include // for setenv 7 | #include // for memcmp, strlen 8 | #include // for timeval 9 | #include // for tzset 10 | #include "gtest/gtest.h" // for EXPECT_EQ, TEST, etc 11 | 12 | using namespace libgep_utils; 13 | 14 | TEST(UtilTest, TimeConversion) { 15 | struct time_conversion_test { 16 | int line; 17 | struct timeval tv; 18 | int64_t usecs; 19 | } test_arr[] = { 20 | {__LINE__, { 0, 0 }, 0 }, 21 | {__LINE__, { 0, 1 }, 1 }, 22 | {__LINE__, { 0, kUsecsPerSec - 1 }, kUsecsPerSec - 1 }, 23 | {__LINE__, { 1, 0 }, kUsecsPerSec }, 24 | {__LINE__, { 1, kUsecsPerSec - 1 }, (2 * kUsecsPerSec) - 1}, 25 | }; 26 | 27 | for (const auto &test_item : test_arr) { 28 | int64_t out_usecs = timeval_to_usecs(&test_item.tv); 29 | EXPECT_EQ(test_item.usecs, out_usecs) << "line " << test_item.line; 30 | struct timeval out_tv = usecs_to_timeval(test_item.usecs); 31 | EXPECT_EQ(out_tv.tv_sec, test_item.tv.tv_sec) << "line " << test_item.line; 32 | EXPECT_EQ(out_tv.tv_usec, test_item.tv.tv_usec) << "line " << 33 | test_item.line; 34 | } 35 | } 36 | 37 | TEST(UtilTest, nice_snprintf) { 38 | char buf[10]; 39 | size_t bufsize = sizeof(buf); 40 | size_t bi = 0; 41 | 42 | // snprintf() returns the number of bytes that would have been written 43 | // had there been enough space in the buffer, not the actual number of 44 | // bytes written 45 | EXPECT_EQ(15, snprintf(buf+bi, bufsize-bi, "1234567890abcde")); 46 | 47 | // nice_snprintf() returns the number of bytes it actually wrote 48 | EXPECT_EQ(10, nice_snprintf(buf+bi, bufsize-bi, "1234567890abcde")); 49 | } 50 | 51 | TEST(UtilTest, snprintf_hex) { 52 | char tmp[1024]; 53 | int bi; 54 | const char *ins = "\x01\x02\x03\x04\x00\x01\xff"; 55 | uint8_t *in = (uint8_t *)ins; 56 | struct snprintf_hex_test { 57 | int line; 58 | int tmp_size; 59 | int in_size; 60 | char *expected_out; 61 | int expected_bi; 62 | } test_arr[] = { 63 | { __LINE__, sizeof(tmp), 4, (char *)"01020304", 8 }, 64 | { __LINE__, sizeof(tmp), 5, (char *)"0102030400", 10 }, 65 | { __LINE__, sizeof(tmp), 6, (char *)"010203040001", 12 }, 66 | { __LINE__, sizeof(tmp), 7, (char *)"010203040001ff", 14 }, 67 | { __LINE__, sizeof(tmp), 0, (char *)"", 0 }, 68 | { __LINE__, sizeof(tmp), -1, (char *)"", 0 }, 69 | { __LINE__, 7, 7, (char *)"010203", 7 }, 70 | { __LINE__, 8, 7, (char *)"0102030", 8 }, 71 | }; 72 | 73 | for (const auto &test_item : test_arr) { 74 | bi = snprintf_hex(tmp, test_item.tmp_size, in, test_item.in_size); 75 | EXPECT_STREQ(test_item.expected_out, tmp) << test_item.line; 76 | EXPECT_EQ(test_item.expected_bi, bi) << test_item.line; 77 | } 78 | } 79 | 80 | TEST(UtilTest, snprintf_printable) { 81 | char tmp[1024]; 82 | int bi; 83 | const char *ins = "\x01\x02" "a\x04\x00\x01\xff"; 84 | uint8_t *in = (uint8_t *)ins; 85 | struct snprintf_printable_test { 86 | int line; 87 | int tmp_size; 88 | int in_size; 89 | char *expected_out; 90 | int expected_bi; 91 | } test_arr[] = { 92 | { __LINE__, sizeof(tmp), 4, (char *)"\\x01\\x02a\\x04", 13 }, 93 | { __LINE__, sizeof(tmp), 5, (char *)"\\x01\\x02a\\x04\\x00", 17 }, 94 | { __LINE__, sizeof(tmp), 6, (char *)"\\x01\\x02a\\x04\\x00\\x01", 21 }, 95 | { __LINE__, sizeof(tmp), 7, (char *)"\\x01\\x02a\\x04\\x00\\x01\\xff", 25 }, 96 | { __LINE__, sizeof(tmp), 0, (char *)"", 0 }, 97 | { __LINE__, sizeof(tmp), -1, (char *)"", 0 }, 98 | { __LINE__, 7, 7, (char *)"\\x01\\x0", 7 }, 99 | { __LINE__, 8, 7, (char *)"\\x01\\x02", 8 }, 100 | { __LINE__, 9, 7, (char *)"\\x01\\x02a", 9 }, 101 | }; 102 | 103 | for (const auto &test_item : test_arr) { 104 | bi = snprintf_printable(tmp, test_item.tmp_size, in, test_item.in_size); 105 | EXPECT_EQ(0, memcmp(tmp, test_item.expected_out, strlen(tmp))) 106 | << test_item.line; 107 | EXPECT_EQ(test_item.expected_bi, bi) << test_item.line; 108 | } 109 | } 110 | 111 | TEST(UtilTest, snprintf_date) { 112 | char tmp[1024]; 113 | struct snprintf_date_test { 114 | int line; 115 | struct timeval tvin; 116 | bool full; 117 | int tmp_size; 118 | char *expected_out; 119 | } test_arr[] = { 120 | // full dates 121 | { __LINE__, {0, 0}, true, sizeof(tmp), (char *)"1969-12-31T16:00:00.000-0800"}, 122 | { __LINE__, {0, 99999}, true, sizeof(tmp), (char *)"1969-12-31T16:00:00.099-0800"}, 123 | { __LINE__, {0, 999999}, true, sizeof(tmp), (char *)"1969-12-31T16:00:00.999-0800"}, 124 | { __LINE__, {1000000000, 0}, true, sizeof(tmp), 125 | (char *)"2001-09-08T18:46:40.000-0700"}, 126 | { __LINE__, {1111111111, 0}, true, sizeof(tmp), 127 | (char *)"2005-03-17T17:58:31.000-0800"}, 128 | { __LINE__, {1425808799, 0}, true, sizeof(tmp), 129 | (char *)"2015-03-08T01:59:59.000-0800"}, 130 | { __LINE__, {1425808800, 0}, true, sizeof(tmp), 131 | (char *)"2015-03-08T03:00:00.000-0700"}, 132 | { __LINE__, {1425808800, 0}, true, 1, (char *)""}, 133 | { __LINE__, {1425808800, 0}, true, 10, (char *)""}, 134 | { __LINE__, {1425808800, 0}, true, 20, (char *)"2015-03-08T03:00:00"}, 135 | { __LINE__, {1425808800, 0}, true, 22, (char *)"2015-03-08T03:00:00.0"}, 136 | { __LINE__, {1425808800, 0}, true, 24, (char *)"2015-03-08T03:00:00.000"}, 137 | { __LINE__, {1425808800, 0}, true, 25, (char *)"2015-03-08T03:00:00.000"}, 138 | { __LINE__, {1425808800, 0}, true, 26, (char *)"2015-03-08T03:00:00.000"}, 139 | // short dates 140 | { __LINE__, {0, 0}, false, sizeof(tmp), (char *)"31,16:00:00.000"}, 141 | { __LINE__, {0, 99999}, false, sizeof(tmp), (char *)"31,16:00:00.099"}, 142 | { __LINE__, {0, 999999}, false, sizeof(tmp), (char *)"31,16:00:00.999"}, 143 | { __LINE__, {1000000000, 0}, false, sizeof(tmp), (char *)"08,18:46:40.000"}, 144 | { __LINE__, {1111111111, 0}, false, sizeof(tmp), (char *)"17,17:58:31.000"}, 145 | { __LINE__, {1425808799, 0}, false, sizeof(tmp), (char *)"08,01:59:59.000"}, 146 | { __LINE__, {1425808800, 0}, false, sizeof(tmp), (char *)"08,03:00:00.000"}, 147 | { __LINE__, {1425808800, 0}, false, 1, (char *)""}, 148 | { __LINE__, {1425808800, 0}, false, 10, (char *)""}, 149 | { __LINE__, {1425808800, 0}, false, 12, (char *)"08,03:00:00"}, 150 | { __LINE__, {1425808800, 0}, false, 14, (char *)"08,03:00:00.0"}, 151 | { __LINE__, {1425808800, 0}, false, 16, (char *)"08,03:00:00.000"}, 152 | { __LINE__, {1425808800, 0}, false, 17, (char *)"08,03:00:00.000"}, 153 | { __LINE__, {1425808800, 0}, false, 18, (char *)"08,03:00:00.000"}, 154 | }; 155 | 156 | // somebody needs to set the TZ for snprintf_date to work 157 | setenv("TZ", "PST8PDT", 1); 158 | tzset(); 159 | 160 | for (const auto &test_item : test_arr) { 161 | snprintf_date(tmp, test_item.tmp_size, &(test_item.tvin), test_item.full); 162 | EXPECT_STREQ(test_item.expected_out, tmp) << test_item.line; 163 | } 164 | } 165 | 166 | int main(int argc, char **argv) { 167 | ::testing::InitGoogleTest(&argc, argv); 168 | return RUN_ALL_TESTS(); 169 | } 170 | --------------------------------------------------------------------------------