├── .gitignore ├── Emakefile ├── LICENSE ├── Makefile ├── README.md ├── ebin └── .gitignore ├── include ├── .gitignore └── enet.hrl ├── priv └── .gitignore ├── rebar.config ├── rebar.lock ├── src ├── enet.app.src ├── enet.erl ├── enet_app.erl ├── enet_channel.erl ├── enet_command.erl ├── enet_commands.hrl ├── enet_constants.hrl ├── enet_disconnector.erl ├── enet_host.erl ├── enet_host_sup.erl ├── enet_peer.erl ├── enet_peer.hrl ├── enet_peer_sup.erl ├── enet_pool.erl ├── enet_protocol.hrl ├── enet_protocol_decode.erl ├── enet_protocol_encode.erl └── enet_sup.erl └── test ├── enet_api_SUITE.erl ├── enet_model.erl └── enet_sync.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .rebar3 2 | _* 3 | .eunit 4 | *.o 5 | *.beam 6 | *.plt 7 | *.swp 8 | *.swo 9 | .erlang.cookie 10 | ebin 11 | log 12 | erl_crash.dump 13 | .rebar 14 | logs 15 | _build 16 | .idea 17 | *.iml 18 | rebar3.crashdump 19 | -------------------------------------------------------------------------------- /Emakefile: -------------------------------------------------------------------------------- 1 | {"src/*", 2 | [ 3 | debug_info, 4 | {i,"include/"}, 5 | {outdir, "ebin/"} 6 | ]}. 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | erl -pa ebin/ -make 3 | 4 | clean: 5 | rm ebin/*.beam 6 | 7 | dialyzer: 8 | dialyzer ebin/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # enet 2 | A complete re-implementation of the [ENet](http://enet.bespin.org/) protocol in Erlang/OTP. 3 | 4 | ## API 5 | The module `enet` presents the API of enet. 6 | 7 | ### Data Types 8 | ```erlang 9 | port_number() = 0..65535 10 | 11 | peer_count() = 1..255 12 | 13 | channel_count() = 1..255 14 | 15 | bytes_per_second() = non_neg_integer() 16 | 17 | channels() = #{ non_neg_integer() := pid() } 18 | ``` 19 | 20 | ### Functions 21 | 22 | #### start_host/3 23 | ```erlang 24 | start_host(Port, ConnectFun, Options) -> {ok, port_number()} | {error, term()} 25 | 26 | Port = port_number() 27 | ConnectFun = mfa() | fun((PeerInfo) -> ConnectFunResult) 28 | PeerInfo = map() 29 | ConnectFunResult = {ok, pid()} | {error, term()} 30 | Options = [Option] 31 | Option = 32 | {peer_limit, peer_count()} | 33 | {channel_limit, channel_count()} | 34 | {incoming_bandwidth, bytes_per_second()} | 35 | {outgoing_bandwidth, bytes_per_second()} 36 | ``` 37 | Start a new host. If `Port` set to `0`, the port will be dynamically assigned by the underlying operating system. The assigned port is returned. 38 | 39 | The `ConnectFun` function or MFA tuple will be called when a new peer has started and a connection to a remote peer has been established. This function is expected to spawn a new process and return a pid to which all messages from the new peer will be sent. 40 | 41 | #### stop_host/1 42 | ```erlang 43 | stop_host(Port) -> ok 44 | 45 | Port = port_number() 46 | ``` 47 | Stop a host listening on `Port`. 48 | 49 | #### connect_peer/4 50 | ```erlang 51 | connect_peer(HostPort, IP, RemotePort, ChannelCount) -> {ok, Peer} | {error, atom()} 52 | 53 | HostPort = port_number() 54 | IP = string() 55 | RemotePort = port_number() 56 | ChannelCount = channel_count() 57 | Peer = pid() 58 | ``` 59 | Start a new peer on the host listening on `HostPort` connecting to a remote host on address `IP:RemotePort`. The peer process will call `ConnectFun` (given to start_host/3) when the handshake has been completed successfully. 60 | 61 | #### disconnect_peer/1 62 | ```erlang 63 | disconnect_peer(Peer) -> ok 64 | 65 | Peer = pid() 66 | ``` 67 | Disconnect `Peer`. 68 | 69 | #### disconnect_peer_now/1 70 | ```erlang 71 | disconnect_peer_now(Peer) -> ok 72 | 73 | Peer = pid() 74 | ``` 75 | Disconnect `Peer` immediately without waiting for an ACK from the remote peer. 76 | 77 | #### send_unsequenced/2 78 | ```erlang 79 | send_unsequenced(Channel, Data) -> ok 80 | 81 | Channel = pid() 82 | Data = iodata() 83 | ``` 84 | Send *unsequenced* data to the remote peer over `Channel`. 85 | 86 | #### send_unreliable/2 87 | ```erlang 88 | send_unreliable(Channel, Data) -> ok 89 | 90 | Channel = pid() 91 | Data = iodata() 92 | ``` 93 | Send *unreliable* data to the remote peer over `Channel`. 94 | 95 | #### send_reliable/2 96 | ```erlang 97 | send_reliable(Channel, Data) -> ok 98 | 99 | Channel = pid() 100 | Data = iodata() 101 | ``` 102 | Send *reliable* data to the remote peer over `Channel`. 103 | 104 | #### broadcast_unsequenced/3 105 | ```erlang 106 | broadcast_unsequenced(HostPort, ChannelID, Data) -> ok 107 | 108 | HostPort = port_number() 109 | ChannelID = integer() 110 | Data = iodata() 111 | ``` 112 | Broadcast *unsequenced* data to all peers connected to `HostPort` on `ChannelID`. 113 | 114 | #### broadcast_unreliable/3 115 | ```erlang 116 | broadcast_unreliable(HostPort, ChannelID, Data) -> ok 117 | 118 | HostPort = port_number() 119 | ChannelID = integer() 120 | Data = iodata() 121 | ``` 122 | Broadcast *unreliable* data to all peers connected to `HostPort` on `ChannelID`. 123 | 124 | #### broadcast_reliable/3 125 | ```erlang 126 | broadcast_reliable(HostPort, ChannelID, Data) -> ok 127 | 128 | HostPort = port_number() 129 | ChannelID = integer() 130 | Data = iodata() 131 | ``` 132 | Broadcast *reliable* data to all peers connected to `HostPort` on `ChannelID`. 133 | 134 | ## Examples 135 | ### Creating an ENet server 136 | ```erlang 137 | ListeningPort = 1234, 138 | ConnectFun = fun(PeerInfo) -> 139 | server_supervisor:start_worker(PeerInfo) 140 | end, 141 | Options = [{peer_limit, 8}, {channel_limit, 3}], 142 | {ok, Host} = enet:start_host(ListeningPort, ConnectFun, Options), 143 | ... 144 | ... 145 | ... 146 | enet:stop_host(Host). 147 | ``` 148 | ### Creating an ENet client and connecting to a server 149 | ```erlang 150 | ListeningPort = 0, %% Port will be dynamically assigned 151 | ConnectFun = fun(PeerInfo) -> 152 | client_supervisor:start_worker(PeerInfo) 153 | end, 154 | Options = [{peer_limit, 1}, {channel_limit, 3}], 155 | {ok, Host} = enet:start_host(ListeningPort, ConnectFun, Options), 156 | ... 157 | ... 158 | RemoteHost = "...." 159 | Port = 1234, 160 | ChannelCount = 3 161 | {ok, Peer} = enet:connect_peer(Host, RemoteHost, Port, ChannelCount), 162 | ... 163 | ... 164 | enet:stop_host(Host). 165 | ``` 166 | ### Sending packets to an ENet peer 167 | ```erlang 168 | worker_loop(PeerInfo = #{channels := Channels}, State) -> 169 | ... 170 | ... 171 | {ok, Channel0} = maps:find(0, Channels), 172 | Data = <<"packet">>, 173 | enet:send_unsequenced(Channel0, Data), 174 | enet:send_unreliable(Channel0, Data), 175 | enet:send_reliable(Channel0, Data), 176 | ... 177 | ... 178 | worker_loop(PeerInfo, State). 179 | ``` 180 | ### Receiving packets from an ENet peer 181 | ```erlang 182 | worker_loop(PeerInfo, State) -> 183 | ... 184 | ... 185 | receive 186 | {enet, ChannelID, #unsequenced{ data = Packet }} -> 187 | %% Handle the Packet 188 | ok; 189 | {enet, ChannelID, #unreliable{ data = Packet }} -> 190 | %% Handle the Packet 191 | ok; 192 | {enet, ChannelID, #reliable{ data = Packet }} -> 193 | %% Handle the Packet 194 | ok 195 | end, 196 | ... 197 | ... 198 | worker_loop(PeerInfo, State). 199 | ``` 200 | -------------------------------------------------------------------------------- /ebin/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flambard/enet/59a564ef3927dc984926e3eb4723415cbda1b2b6/ebin/.gitignore -------------------------------------------------------------------------------- /include/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flambard/enet/59a564ef3927dc984926e3eb4723415cbda1b2b6/include/.gitignore -------------------------------------------------------------------------------- /include/enet.hrl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Send Reliable Command 3 | %% 4 | 5 | -record(reliable, 6 | { 7 | data = <<>> 8 | }). 9 | 10 | 11 | %% 12 | %% Send Unreliable Command 13 | %% 14 | 15 | -record(unreliable, 16 | { 17 | sequence_number = 0, 18 | data = <<>> 19 | }). 20 | 21 | 22 | %% 23 | %% Send Unsequenced Command 24 | %% 25 | 26 | -record(unsequenced, 27 | { 28 | group = 0, 29 | data = <<>> 30 | }). 31 | -------------------------------------------------------------------------------- /priv/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flambard/enet/59a564ef3927dc984926e3eb4723415cbda1b2b6/priv/.gitignore -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {deps, [ 2 | {gproc, {git, "https://github.com/uwiger/gproc.git", {branch, "master"}}} 3 | ] 4 | }. 5 | 6 | {shell, [ 7 | {apps, [enet]} 8 | ]}. 9 | 10 | {profiles, 11 | [ 12 | {test, [ 13 | {deps, [ 14 | {proper, "1.3.0"} 15 | ]}, 16 | {erl_opts, [debug_info]}, 17 | {shell, [ 18 | {apps, [enet, proper]} 19 | ]} 20 | ]} 21 | ]}. 22 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | [{<<"gproc">>, 2 | {git,"https://github.com/uwiger/gproc.git", 3 | {ref,"af7a1f5486c92ad34a07ddb3c4a1e9a9707763b6"}}, 4 | 0}]. 5 | -------------------------------------------------------------------------------- /src/enet.app.src: -------------------------------------------------------------------------------- 1 | {application, enet, 2 | [{description, "ENet - Reliable UDP networking library"}, 3 | {vsn, "0.0.1"}, 4 | {registered, [ enet_sup ]}, 5 | {modules, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib, 9 | crypto, 10 | gproc 11 | ]}, 12 | {mod, {enet_app, []}}, 13 | {env, []} 14 | ]}. 15 | -------------------------------------------------------------------------------- /src/enet.erl: -------------------------------------------------------------------------------- 1 | -module(enet). 2 | 3 | -export([ 4 | start_host/3, 5 | stop_host/1, 6 | connect_peer/4, 7 | await_connect/0, 8 | disconnect_peer/1, 9 | disconnect_peer_now/1, 10 | send_unsequenced/2, 11 | send_unreliable/2, 12 | send_reliable/2, 13 | broadcast_unsequenced/3, 14 | broadcast_unreliable/3, 15 | broadcast_reliable/3 16 | ]). 17 | 18 | -type port_number() :: 0..65535. 19 | 20 | 21 | %%%=================================================================== 22 | %%% API 23 | %%%=================================================================== 24 | 25 | -spec start_host(Port :: port_number(), 26 | ConnectFun :: mfa() 27 | | fun((map()) -> {ok, pid()} | {error, term()}), 28 | Options :: [{atom(), term()}]) -> 29 | {ok, port_number()} | {error, term()}. 30 | 31 | start_host(Port, ConnectFun, Options) -> 32 | {ok, Socket} = gen_udp:open(Port, enet_host:socket_options()), 33 | {ok, AssignedPort} = inet:port(Socket), 34 | case enet_sup:start_host_supervisor(AssignedPort, ConnectFun, Options) of 35 | {error, Reason} -> {error, Reason}; 36 | {ok, _HostSup} -> 37 | Host = gproc:where({n, l, {enet_host, AssignedPort}}), 38 | enet_host:give_socket(Host, Socket), 39 | {ok, AssignedPort} 40 | end. 41 | 42 | 43 | -spec stop_host(HostPort :: port_number()) -> ok. 44 | 45 | stop_host(HostPort) -> 46 | enet_sup:stop_host_supervisor(HostPort). 47 | 48 | 49 | -spec connect_peer(HostPort :: port_number(), 50 | IP :: string(), 51 | RemotePort :: port_number(), 52 | ChannelCount :: pos_integer()) -> 53 | {ok, pid()} | {error, atom()}. 54 | 55 | connect_peer(HostPort, IP, RemotePort, ChannelCount) -> 56 | Host = gproc:where({n, l, {enet_host, HostPort}}), 57 | enet_host:connect(Host, IP, RemotePort, ChannelCount). 58 | 59 | 60 | await_connect() -> 61 | receive 62 | C = {enet, connect, _LocalOrRemote, _PC, _ConnectID} -> {ok, C} 63 | after 1000 -> {error, timeout} 64 | end. 65 | 66 | 67 | -spec disconnect_peer(Peer :: pid()) -> ok. 68 | 69 | disconnect_peer(Peer) -> 70 | enet_peer:disconnect(Peer). 71 | 72 | 73 | -spec disconnect_peer_now(Peer :: pid()) -> ok. 74 | 75 | disconnect_peer_now(Peer) -> 76 | enet_peer:disconnect_now(Peer). 77 | 78 | 79 | -spec send_unsequenced(Channel :: pid(), Data :: iodata()) -> ok. 80 | 81 | send_unsequenced(Channel, Data) -> 82 | enet_channel:send_unsequenced(Channel, Data). 83 | 84 | 85 | -spec send_unreliable(Channel :: pid(), Data :: iodata()) -> ok. 86 | 87 | send_unreliable(Channel, Data) -> 88 | enet_channel:send_unreliable(Channel, Data). 89 | 90 | 91 | -spec send_reliable(Channel :: pid(), Data :: iodata()) -> ok. 92 | 93 | send_reliable(Channel, Data) -> 94 | enet_channel:send_reliable(Channel, Data). 95 | 96 | 97 | -spec broadcast_unsequenced(HostPort :: port_number(), 98 | ChannelID :: integer(), 99 | Data :: iodata()) -> ok. 100 | 101 | broadcast_unsequenced(HostPort, ChannelID, Data) -> 102 | broadcast(HostPort, ChannelID, Data, fun send_unsequenced/2). 103 | 104 | 105 | -spec broadcast_unreliable(HostPort :: port_number(), 106 | ChannelID :: integer(), 107 | Data :: iodata()) -> ok. 108 | 109 | broadcast_unreliable(HostPort, ChannelID, Data) -> 110 | broadcast(HostPort, ChannelID, Data, fun send_unreliable/2). 111 | 112 | 113 | -spec broadcast_reliable(HostPort :: port_number(), 114 | ChannelID :: integer(), 115 | Data :: iodata()) -> ok. 116 | 117 | broadcast_reliable(HostPort, ChannelID, Data) -> 118 | broadcast(HostPort, ChannelID, Data, fun send_reliable/2). 119 | 120 | 121 | %%%=================================================================== 122 | %%% Internal functions 123 | %%%=================================================================== 124 | 125 | broadcast(HostPort, ChannelID, Data, SendFun) -> 126 | Peers = enet_pool:active_peers(HostPort), 127 | lists:foreach(fun ({_Name, Peer}) -> 128 | Channel = enet_peer:channel(Peer, ChannelID), 129 | SendFun(Channel, Data) 130 | end, 131 | Peers). 132 | -------------------------------------------------------------------------------- /src/enet_app.erl: -------------------------------------------------------------------------------- 1 | -module(enet_app). 2 | -behaviour(application). 3 | 4 | %% Application callbacks 5 | -export([ 6 | start/2, 7 | stop/1 8 | ]). 9 | 10 | 11 | %%%=================================================================== 12 | %%% Application callbacks 13 | %%%=================================================================== 14 | 15 | start(_StartType, _StartArgs) -> 16 | case enet_sup:start_link() of 17 | {ok, Pid} -> {ok, Pid}; 18 | Error -> Error 19 | end. 20 | 21 | stop(_State) -> 22 | ok. 23 | 24 | 25 | %%%=================================================================== 26 | %%% Internal functions 27 | %%%=================================================================== 28 | -------------------------------------------------------------------------------- /src/enet_channel.erl: -------------------------------------------------------------------------------- 1 | -module(enet_channel). 2 | 3 | -include("enet_commands.hrl"). 4 | 5 | -export([ 6 | start_link/2, 7 | stop/1, 8 | set_worker/2, 9 | recv_unsequenced/2, 10 | send_unsequenced/2, 11 | recv_unreliable/2, 12 | send_unreliable/2, 13 | recv_reliable/2, 14 | send_reliable/2 15 | ]). 16 | 17 | -export([ 18 | init/3 19 | ]). 20 | 21 | -export([ 22 | system_code_change/4, 23 | system_continue/3, 24 | system_terminate/4, 25 | write_debug/3 26 | ]). 27 | 28 | -record(state, 29 | { 30 | id, 31 | peer, 32 | worker, 33 | incoming_reliable_sequence_number = 1, 34 | incoming_unreliable_sequence_number = 0, 35 | outgoing_reliable_sequence_number = 1, 36 | outgoing_unreliable_sequence_number = 1, 37 | reliable_windows, %% reliableWindows [ENET_PEER_RELIABLE_WINDOWS] (uint16 * 16 = 32 bytes) 38 | used_reliable_windows = 0, 39 | sys_parent, 40 | sys_debug 41 | }). 42 | 43 | 44 | %%% 45 | %%% API 46 | %%% 47 | 48 | start_link(ID, Peer) -> 49 | proc_lib:start_link(?MODULE, init, [ID, Peer, self()]). 50 | 51 | stop(Channel) -> 52 | Channel ! stop. 53 | 54 | set_worker(Channel, Worker) -> 55 | Channel ! {set_worker, Worker}. 56 | 57 | recv_unsequenced(Channel, {H, C}) -> 58 | %% Peer -> Channel -> Worker 59 | Channel ! {recv_unsequenced, {H, C}}, 60 | ok. 61 | 62 | send_unsequenced(Channel, Data) -> 63 | %% Worker -> Channel -> Peer 64 | Channel ! {send_unsequenced, Data}, 65 | ok. 66 | 67 | recv_unreliable(Channel, {H, C}) -> 68 | %% Peer -> Channel -> Worker 69 | Channel ! {recv_unreliable, {H, C}}, 70 | ok. 71 | 72 | send_unreliable(Channel, Data) -> 73 | %% Worker -> Channel -> Peer 74 | Channel ! {send_unreliable, Data}, 75 | ok. 76 | 77 | recv_reliable(Channel, {H, C}) -> 78 | %% Peer -> Channel -> Worker 79 | Channel ! {recv_reliable, {H, C}}, 80 | ok. 81 | 82 | send_reliable(Channel, Data) -> 83 | %% Worker -> Channel -> Peer 84 | Channel ! {send_reliable, Data}, 85 | ok. 86 | 87 | 88 | %%% 89 | %%% Implementation 90 | %%% 91 | 92 | init(ID, Peer, Parent) -> 93 | Debug = sys:debug_options([]), 94 | State = #state{ 95 | id = ID, 96 | peer = Peer, 97 | sys_parent = Parent, 98 | sys_debug = Debug 99 | }, 100 | proc_lib:init_ack(Parent, {ok, self()}), 101 | await_worker(State). 102 | 103 | 104 | await_worker(S) -> 105 | receive 106 | {set_worker, Worker} -> loop(S#state{ worker = Worker }) 107 | end. 108 | 109 | 110 | loop(S = #state{ id = ID, peer = Peer, worker = Worker }) -> 111 | receive 112 | 113 | {system, From, Request} -> 114 | #state{ sys_parent = Parent, sys_debug = Debug } = S, 115 | sys:handle_system_msg(Request, From, Parent, ?MODULE, Debug, S); 116 | 117 | {recv_unsequenced, { 118 | #command_header{ unsequenced = 1 }, 119 | C = #unsequenced{} 120 | }} -> 121 | Worker ! {enet, ID, C}, 122 | loop(S); 123 | {send_unsequenced, Data} -> 124 | {H, C} = enet_command:send_unsequenced(ID, Data), 125 | ok = enet_peer:send_command(Peer, {H, C}), 126 | loop(S); 127 | 128 | {recv_unreliable, { 129 | #command_header{}, 130 | C = #unreliable{ sequence_number = N } 131 | }} -> 132 | if N < S#state.incoming_unreliable_sequence_number -> 133 | %% Data is old - drop it and continue. 134 | loop(S); 135 | true -> 136 | Worker ! {enet, ID, C}, 137 | NewS = S#state{ incoming_unreliable_sequence_number = N }, 138 | loop(NewS) 139 | end; 140 | {send_unreliable, Data} -> 141 | N = S#state.outgoing_unreliable_sequence_number, 142 | {H, C} = enet_command:send_unreliable(ID, N, Data), 143 | ok = enet_peer:send_command(Peer, {H, C}), 144 | NewS = S#state{ outgoing_unreliable_sequence_number = N + 1 }, 145 | loop(NewS); 146 | 147 | {recv_reliable, { 148 | #command_header{ reliable_sequence_number = N }, 149 | C = #reliable{} 150 | }} when N =:= S#state.incoming_reliable_sequence_number -> 151 | Worker ! {enet, ID, C}, 152 | NewS = S#state{ incoming_reliable_sequence_number = N + 1 }, 153 | loop(NewS); 154 | {send_reliable, Data} -> 155 | N = S#state.outgoing_reliable_sequence_number, 156 | {H, C} = enet_command:send_reliable(ID, N, Data), 157 | ok = enet_peer:send_command(Peer, {H, C}), 158 | NewS = S#state{ outgoing_reliable_sequence_number = N + 1 }, 159 | loop(NewS); 160 | 161 | stop -> 162 | stopped 163 | end. 164 | 165 | 166 | %%% 167 | %%% System message handling 168 | %%% 169 | 170 | write_debug(Dev, Event, Name) -> 171 | io:format(Dev, "~p event = ~p~n", [Name, Event]). 172 | 173 | system_continue(_Parent, _Debug, State) -> 174 | loop(State). 175 | 176 | system_terminate(Reason, _Parent, _Debug, _State) -> 177 | exit(Reason). 178 | 179 | system_code_change(State, _Module, _OldVsn, _Extra) -> 180 | {ok, State}. 181 | -------------------------------------------------------------------------------- /src/enet_command.erl: -------------------------------------------------------------------------------- 1 | -module(enet_command). 2 | 3 | -include("enet_commands.hrl"). 4 | -include("enet_protocol.hrl"). 5 | 6 | -export([ 7 | acknowledge/2, 8 | connect/12, 9 | verify_connect/8, 10 | sequenced_disconnect/0, 11 | unsequenced_disconnect/0, 12 | ping/0, 13 | send_unsequenced/2, 14 | send_unreliable/3, 15 | send_reliable/3 16 | ]). 17 | 18 | 19 | acknowledge(H = #command_header{}, SentTime) -> 20 | #command_header{ 21 | channel_id = ChannelID, 22 | reliable_sequence_number = N 23 | } = H, 24 | { 25 | #command_header{ 26 | command = ?COMMAND_ACKNOWLEDGE, 27 | channel_id = ChannelID 28 | }, 29 | #acknowledge{ 30 | received_reliable_sequence_number = N, 31 | received_sent_time = SentTime 32 | } 33 | }. 34 | 35 | 36 | connect(OutgoingPeerID, 37 | IncomingSessionID, 38 | OutgoingSessionID, 39 | ChannelCount, 40 | MTU, 41 | IncomingBandwidth, 42 | OutgoingBandwidth, 43 | PacketThrottleInterval, 44 | PacketThrottleAcceleration, 45 | PacketThrottleDeceleration, 46 | ConnectID, 47 | OutgoingReliableSequenceNumber) -> 48 | WindowSize = calculate_initial_window_size(OutgoingBandwidth), 49 | { 50 | #command_header{ 51 | command = ?COMMAND_CONNECT, 52 | please_acknowledge = 1, 53 | reliable_sequence_number = OutgoingReliableSequenceNumber 54 | }, 55 | #connect{ 56 | outgoing_peer_id = OutgoingPeerID, 57 | incoming_session_id = IncomingSessionID, 58 | outgoing_session_id = OutgoingSessionID, 59 | mtu = MTU, 60 | window_size = WindowSize, 61 | channel_count = ChannelCount, 62 | incoming_bandwidth = IncomingBandwidth, 63 | outgoing_bandwidth = OutgoingBandwidth, 64 | packet_throttle_interval = PacketThrottleInterval, 65 | packet_throttle_acceleration = PacketThrottleAcceleration, 66 | packet_throttle_deceleration = PacketThrottleDeceleration, 67 | connect_id = ConnectID, 68 | data = 0 %% What is this used for? 69 | } 70 | }. 71 | 72 | 73 | verify_connect(C = #connect{}, 74 | OutgoingPeerID, 75 | IncomingSessionID, 76 | OutgoingSessionID, 77 | HostChannelLimit, 78 | IncomingBandwidth, 79 | OutgoingBandwidth, 80 | OutgoingReliableSequenceNumber) -> 81 | WindowSize = 82 | calculate_window_size(IncomingBandwidth, C#connect.window_size), 83 | ISID = 84 | calculate_session_id(C#connect.incoming_session_id, OutgoingSessionID), 85 | OSID = 86 | calculate_session_id(C#connect.outgoing_session_id, IncomingSessionID), 87 | { 88 | #command_header{ 89 | command = ?COMMAND_VERIFY_CONNECT, 90 | please_acknowledge = 1, 91 | reliable_sequence_number = OutgoingReliableSequenceNumber 92 | }, 93 | #verify_connect{ 94 | outgoing_peer_id = OutgoingPeerID, 95 | incoming_session_id = ISID, 96 | outgoing_session_id = OSID, 97 | mtu = clamp(C#connect.mtu, ?MAX_MTU, ?MIN_MTU), 98 | window_size = WindowSize, 99 | channel_count = min(C#connect.channel_count, HostChannelLimit), 100 | incoming_bandwidth = IncomingBandwidth, 101 | outgoing_bandwidth = OutgoingBandwidth, 102 | packet_throttle_interval = C#connect.packet_throttle_interval, 103 | packet_throttle_acceleration = C#connect.packet_throttle_acceleration, 104 | packet_throttle_deceleration = C#connect.packet_throttle_deceleration, 105 | connect_id = C#connect.connect_id 106 | } 107 | }. 108 | 109 | 110 | sequenced_disconnect() -> 111 | { 112 | #command_header{ 113 | please_acknowledge = 1, 114 | command = ?COMMAND_DISCONNECT 115 | }, 116 | #disconnect{} 117 | }. 118 | 119 | 120 | unsequenced_disconnect() -> 121 | { 122 | #command_header{ 123 | unsequenced = 1, 124 | command = ?COMMAND_DISCONNECT 125 | }, 126 | #disconnect{} 127 | }. 128 | 129 | 130 | ping() -> 131 | { 132 | #command_header{ 133 | unsequenced = 1, 134 | command = ?COMMAND_PING 135 | }, 136 | #ping{} 137 | }. 138 | 139 | 140 | send_unsequenced(ChannelID, Data) -> 141 | { 142 | #command_header{ 143 | unsequenced = 1, 144 | command = ?COMMAND_SEND_UNSEQUENCED, 145 | channel_id = ChannelID 146 | }, 147 | #unsequenced{ 148 | data = Data 149 | } 150 | }. 151 | 152 | send_unreliable(ChannelID, SequenceNumber, Data) -> 153 | { 154 | #command_header{ 155 | command = ?COMMAND_SEND_UNRELIABLE, 156 | channel_id = ChannelID 157 | }, 158 | #unreliable{ 159 | sequence_number = SequenceNumber, 160 | data = Data 161 | } 162 | }. 163 | 164 | send_reliable(ChannelID, ReliableSequenceNumber, Data) -> 165 | { 166 | #command_header{ 167 | command = ?COMMAND_SEND_RELIABLE, 168 | channel_id = ChannelID, 169 | reliable_sequence_number = ReliableSequenceNumber 170 | }, 171 | #reliable{ 172 | data = Data 173 | } 174 | }. 175 | 176 | 177 | %%% 178 | %%% Internal functions 179 | %%% 180 | 181 | clamp(X, Max, Min) -> 182 | max(Min, min(Max, X)). 183 | 184 | select_smallest(A, B, Max, Min) -> 185 | clamp(min(A, B), Max, Min). 186 | 187 | 188 | calculate_window_size(0, ConnectWindowSize) -> 189 | clamp(ConnectWindowSize, ?MAX_WINDOW_SIZE, ?MIN_WINDOW_SIZE); 190 | calculate_window_size(IncomingBandwidth, ConnectWindowSize) -> 191 | InitialWindowSize = 192 | ?MIN_WINDOW_SIZE * IncomingBandwidth / ?PEER_WINDOW_SIZE_SCALE, 193 | select_smallest(InitialWindowSize, 194 | ConnectWindowSize, 195 | ?MAX_WINDOW_SIZE, 196 | ?MIN_WINDOW_SIZE). 197 | 198 | calculate_initial_window_size(0) -> 199 | ?MAX_WINDOW_SIZE; 200 | calculate_initial_window_size(OutgoingBandwidth) -> 201 | InitialWindowSize = 202 | ?MAX_WINDOW_SIZE * OutgoingBandwidth / ?PEER_WINDOW_SIZE_SCALE, 203 | clamp(InitialWindowSize, ?MAX_WINDOW_SIZE, ?MIN_WINDOW_SIZE). 204 | 205 | 206 | 207 | calculate_session_id(ConnectSessionID, PeerSessionID) -> 208 | InitialSessionID = 209 | case ConnectSessionID of 210 | 16#FF -> PeerSessionID; 211 | _ -> ConnectSessionID 212 | end, 213 | case (InitialSessionID + 1) band 2#11 of 214 | PeerSessionID -> (PeerSessionID + 1) band 2#11; 215 | IncomingSessionID -> IncomingSessionID 216 | end. 217 | -------------------------------------------------------------------------------- /src/enet_commands.hrl: -------------------------------------------------------------------------------- 1 | -include_lib("enet/include/enet.hrl"). 2 | -include("enet_constants.hrl"). 3 | 4 | %% 5 | %% Protocol Header 6 | %% 7 | 8 | -record(protocol_header, 9 | { 10 | compressed = 0, 11 | session_id = 0, 12 | peer_id = ?MAX_PEER_ID, 13 | sent_time = undefined, 14 | checksum = undefined 15 | }). 16 | 17 | 18 | %% 19 | %% Command Header 20 | %% 21 | 22 | -record(command_header, 23 | { 24 | please_acknowledge = 0, 25 | unsequenced = 0, 26 | command = 0, 27 | channel_id = 16#FF, 28 | reliable_sequence_number = 0 29 | }). 30 | 31 | 32 | %% 33 | %% Acknowledge Command 34 | %% 35 | 36 | -record(acknowledge, 37 | { 38 | received_reliable_sequence_number = 0, 39 | received_sent_time = 0 40 | }). 41 | 42 | 43 | %% 44 | %% Connect Command 45 | %% 46 | 47 | -record(connect, 48 | { 49 | outgoing_peer_id = 0, 50 | incoming_session_id = 0, 51 | outgoing_session_id = 0, 52 | mtu = ?MIN_MTU, 53 | window_size = ?MIN_WINDOW_SIZE, 54 | channel_count = ?MIN_CHANNEL_COUNT, 55 | incoming_bandwidth = 0, 56 | outgoing_bandwidth = 0, 57 | packet_throttle_interval = 0, 58 | packet_throttle_acceleration = 0, 59 | packet_throttle_deceleration = 0, 60 | connect_id = 0, 61 | data = 0 62 | }). 63 | 64 | 65 | %% 66 | %% Verify Connect Command 67 | %% 68 | 69 | -record(verify_connect, 70 | { 71 | outgoing_peer_id = 0, 72 | incoming_session_id = 0, 73 | outgoing_session_id = 0, 74 | mtu = ?MIN_MTU, 75 | window_size = ?MIN_WINDOW_SIZE, 76 | channel_count = ?MIN_CHANNEL_COUNT, 77 | incoming_bandwidth = 0, 78 | outgoing_bandwidth = 0, 79 | packet_throttle_interval = 0, 80 | packet_throttle_acceleration = 0, 81 | packet_throttle_deceleration = 0, 82 | connect_id = 0 83 | }). 84 | 85 | 86 | %% 87 | %% Disconnect Command 88 | %% 89 | 90 | -record(disconnect, 91 | { 92 | data = 0 93 | }). 94 | 95 | 96 | %% 97 | %% Ping Command 98 | %% 99 | 100 | -record(ping, 101 | { 102 | }). 103 | 104 | 105 | %% 106 | %% Send Fragment Command 107 | %% 108 | 109 | -record(fragment, 110 | { 111 | start_sequence_number = 0, 112 | fragment_count = 0, 113 | fragment_number = 0, 114 | total_length = 0, 115 | fragment_offset = 0, 116 | data = <<>> 117 | }). 118 | 119 | 120 | %% 121 | %% Bandwidth Limit Command 122 | %% 123 | 124 | -record(bandwidth_limit, 125 | { 126 | incoming_bandwidth = 0, 127 | outgoing_bandwidth = 0 128 | }). 129 | 130 | 131 | %% 132 | %% Throttle Configure Command 133 | %% 134 | 135 | -record(throttle_configure, 136 | { 137 | packet_throttle_interval = 0, 138 | packet_throttle_acceleration = 0, 139 | packet_throttle_deceleration = 0 140 | }). 141 | -------------------------------------------------------------------------------- /src/enet_constants.hrl: -------------------------------------------------------------------------------- 1 | 2 | %%% 3 | %%% Limits 4 | %%% 5 | 6 | -define(MIN_MTU, 576). 7 | -define(MAX_MTU, 4096). 8 | -define(MAX_PACKET_COMMANDS, 32). 9 | -define(MIN_WINDOW_SIZE, 4096). 10 | -define(MAX_WINDOW_SIZE, 65536). 11 | -define(MIN_CHANNEL_COUNT, 1). 12 | -define(MAX_CHANNEL_COUNT, 255). 13 | -define(MAX_PEER_ID, 16#FFF). 14 | -define(MAX_FRAGMENT_COUNT, 1048575). %% 1024 * 1024 15 | 16 | 17 | %%% 18 | %%% Defaults 19 | %%% 20 | 21 | -define(HOST_RECEIVE_BUFFER_SIZE , 256 * 1024). 22 | -define(HOST_SEND_BUFFER_SIZE , 256 * 1024). 23 | -define(HOST_BANDWIDTH_THROTTLE_INTERVAL , 1000). 24 | -define(HOST_DEFAULT_MTU , 1400). 25 | -define(HOST_DEFAULT_MAXIMUM_PACKET_SIZE , 32 * 1024 * 1024). 26 | -define(HOST_DEFAULT_MAXIMUM_WAITING_DATA , 32 * 1024 * 1024). 27 | 28 | -define(PEER_DEFAULT_ROUND_TRIP_TIME , 500). 29 | -define(PEER_DEFAULT_PACKET_THROTTLE , 32). 30 | -define(PEER_PACKET_THROTTLE_SCALE , 32). 31 | -define(PEER_PACKET_THROTTLE_COUNTER , 7). 32 | -define(PEER_PACKET_THROTTLE_ACCELERATION , 2). 33 | -define(PEER_PACKET_THROTTLE_DECELERATION , 2). 34 | -define(PEER_PACKET_THROTTLE_INTERVAL , 5000). 35 | -define(PEER_PACKET_LOSS_SCALE , (1 bsl 16)). 36 | -define(PEER_PACKET_LOSS_INTERVAL , 10000). 37 | -define(PEER_WINDOW_SIZE_SCALE , (64 * 1024)). 38 | -define(PEER_TIMEOUT_LIMIT , 32). 39 | -define(PEER_TIMEOUT_MINIMUM , 5000). 40 | -define(PEER_TIMEOUT_MAXIMUM , 30000). 41 | -define(PEER_PING_INTERVAL , 500). 42 | -define(PEER_UNSEQUENCED_WINDOWS , 64). 43 | -define(PEER_UNSEQUENCED_WINDOW_SIZE , 1024). 44 | -define(PEER_FREE_UNSEQUENCED_WINDOWS , 32). 45 | -define(PEER_RELIABLE_WINDOWS , 16). 46 | -define(PEER_RELIABLE_WINDOW_SIZE , 16#1000). 47 | -define(PEER_FREE_RELIABLE_WINDOWS , 8). 48 | -------------------------------------------------------------------------------- /src/enet_disconnector.erl: -------------------------------------------------------------------------------- 1 | -module(enet_disconnector). 2 | -behaviour(gen_server). 3 | 4 | %% API 5 | -export([ 6 | start_link/1, 7 | set_trigger/4, 8 | unset_trigger/4 9 | ]). 10 | 11 | %% gen_server callbacks 12 | -export([ 13 | init/1, 14 | handle_call/3, 15 | handle_cast/2, 16 | handle_info/2, 17 | terminate/2 18 | ]). 19 | 20 | -record(state, 21 | { 22 | port 23 | }). 24 | 25 | 26 | %%%=================================================================== 27 | %%% API 28 | %%%=================================================================== 29 | 30 | start_link(LocalPort) -> 31 | gen_server:start_link(?MODULE, [LocalPort], []). 32 | 33 | set_trigger(LocalPort, PeerID, IP, Port) -> 34 | Server = gproc:where({n, l, {enet_disconnector, LocalPort}}), 35 | gen_server:cast(Server, {set_trigger, self(), PeerID, IP, Port}). 36 | 37 | unset_trigger(LocalPort, PeerID, IP, Port) -> 38 | Server = gproc:where({n, l, {enet_disconnector, LocalPort}}), 39 | gen_server:call(Server, {unset_trigger, PeerID, IP, Port}). 40 | 41 | 42 | %%%=================================================================== 43 | %%% gen_server callbacks 44 | %%%=================================================================== 45 | 46 | init([LocalPort]) -> 47 | true = gproc:reg({n, l, {enet_disconnector, LocalPort}}), 48 | {ok, #state{ port = LocalPort }}. 49 | 50 | 51 | handle_call({unset_trigger, PeerID, IP, Port}, {PeerPid, _}, S) -> 52 | Key = {n, l, {PeerID, IP, Port}}, 53 | Ref = gproc:get_value(Key, PeerPid), 54 | ok = gproc:demonitor(Key, Ref), 55 | true = gproc:unreg_other(Key, PeerPid), 56 | {reply, ok, S}. 57 | 58 | 59 | handle_cast({set_trigger, PeerPid, PeerID, IP, Port}, State) -> 60 | Key = {n, l, {PeerID, IP, Port}}, 61 | true = gproc:reg_other(Key, PeerPid), 62 | Ref = gproc:monitor(Key), 63 | updated = gproc:ensure_reg_other(Key, PeerPid, Ref), 64 | {noreply, State}. 65 | 66 | 67 | handle_info({gproc, unreg, _Ref, {n, l, {PeerID, IP, Port}}}, S) -> 68 | #state{ port = LocalPort } = S, 69 | {CH, Command} = enet_command:unsequenced_disconnect(), 70 | HBin = enet_protocol_encode:command_header(CH), 71 | CBin = enet_protocol_encode:command(Command), 72 | Data = [HBin, CBin], 73 | Host = gproc:where({n, l, {enet_host, LocalPort}}), 74 | enet_host:send_outgoing_commands(Host, Data, IP, Port, PeerID), 75 | {noreply, S}. 76 | 77 | 78 | terminate(_Reason, _State) -> 79 | ok. 80 | 81 | 82 | %%%=================================================================== 83 | %%% Internal functions 84 | %%%=================================================================== 85 | -------------------------------------------------------------------------------- /src/enet_host.erl: -------------------------------------------------------------------------------- 1 | -module(enet_host). 2 | -behaviour(gen_server). 3 | 4 | -include("enet_peer.hrl"). 5 | -include("enet_commands.hrl"). 6 | -include("enet_protocol.hrl"). 7 | 8 | %% API 9 | -export([ 10 | start_link/3, 11 | socket_options/0, 12 | give_socket/2, 13 | connect/4, 14 | send_outgoing_commands/4, 15 | send_outgoing_commands/5, 16 | get_port/1, 17 | get_incoming_bandwidth/1, 18 | get_outgoing_bandwidth/1, 19 | get_mtu/1, 20 | get_channel_limit/1 21 | ]). 22 | 23 | %% gen_server callbacks 24 | -export([ 25 | init/1, 26 | handle_call/3, 27 | handle_cast/2, 28 | handle_info/2, 29 | terminate/2, 30 | code_change/3 31 | ]). 32 | 33 | -record(state, 34 | { 35 | socket, 36 | compress_fun, 37 | decompress_fun, 38 | connect_fun 39 | }). 40 | 41 | -define(NULL_PEER_ID, ?MAX_PEER_ID). 42 | 43 | 44 | %%%=================================================================== 45 | %%% API 46 | %%%=================================================================== 47 | 48 | start_link(Port, ConnectFun, Options) -> 49 | gen_server:start_link(?MODULE, {Port, ConnectFun, Options}, []). 50 | 51 | socket_options() -> 52 | [binary, {active, false}, {reuseaddr, false}, {broadcast, true}]. 53 | 54 | give_socket(Host, Socket) -> 55 | ok = gen_udp:controlling_process(Socket, Host), 56 | gen_server:cast(Host, {give_socket, Socket}). 57 | 58 | connect(Host, IP, Port, ChannelCount) -> 59 | gen_server:call(Host, {connect, IP, Port, ChannelCount}). 60 | 61 | send_outgoing_commands(Host, Commands, IP, Port) -> 62 | send_outgoing_commands(Host, Commands, IP, Port, ?NULL_PEER_ID). 63 | 64 | send_outgoing_commands(Host, Commands, IP, Port, PeerID) -> 65 | gen_server:call(Host, {send_outgoing_commands, Commands, IP, Port, PeerID}). 66 | 67 | get_port(Host) -> 68 | gproc:get_value({p, l, port}, Host). 69 | 70 | get_incoming_bandwidth(Host) -> 71 | gproc:get_value({p, l, incoming_bandwidth}, Host). 72 | 73 | get_outgoing_bandwidth(Host) -> 74 | gproc:get_value({p, l, outgoing_bandwidth}, Host). 75 | 76 | get_mtu(Host) -> 77 | gproc:get_value({p, l, mtu}, Host). 78 | 79 | get_channel_limit(Host) -> 80 | gproc:get_value({p, l, channel_limit}, Host). 81 | 82 | 83 | %%%=================================================================== 84 | %%% gen_server callbacks 85 | %%%=================================================================== 86 | 87 | init({AssignedPort, ConnectFun, Options}) -> 88 | true = gproc:reg({n, l, {enet_host, AssignedPort}}), 89 | ChannelLimit = 90 | case lists:keyfind(channel_limit, 1, Options) of 91 | {channel_limit, CLimit} -> CLimit; 92 | false -> ?MIN_CHANNEL_COUNT 93 | end, 94 | IncomingBandwidth = 95 | case lists:keyfind(incoming_bandwidth, 1, Options) of 96 | {incoming_bandwidth, IBandwidth} -> IBandwidth; 97 | false -> 0 98 | end, 99 | OutgoingBandwidth = 100 | case lists:keyfind(outgoing_bandwidth, 1, Options) of 101 | {outgoing_bandwidth, OBandwidth} -> OBandwidth; 102 | false -> 0 103 | end, 104 | true = gproc:mreg(p, l, 105 | [ 106 | {port, AssignedPort}, 107 | {channel_limit, ChannelLimit}, 108 | {incoming_bandwidth, IncomingBandwidth}, 109 | {outgoing_bandwidth, OutgoingBandwidth}, 110 | {mtu, ?HOST_DEFAULT_MTU} 111 | ]), 112 | case gen_udp:open(AssignedPort, socket_options()) of 113 | {error, eaddrinuse} -> 114 | %% 115 | %% A socket has already been opened on this port 116 | %% - The socket will be given to us later 117 | %% 118 | {ok, #state{ connect_fun = ConnectFun }}; 119 | {ok, Socket} -> 120 | %% 121 | %% We were able to open a new socket on this port 122 | %% - It means we have been restarted by the supervisor 123 | %% - Set it to active mode 124 | %% 125 | ok = inet:setopts(Socket, [{active, true}]), 126 | {ok, #state{ connect_fun = ConnectFun, socket = Socket }} 127 | end. 128 | 129 | 130 | handle_call({connect, IP, Port, Channels}, _From, S) -> 131 | %% 132 | %% Connect to a remote peer. 133 | %% 134 | %% - Add a peer to the pool 135 | %% - Start the peer process 136 | %% 137 | #state{ 138 | connect_fun = ConnectFun 139 | } = S, 140 | Ref = make_ref(), 141 | LocalPort = get_port(self()), 142 | Reply = 143 | try enet_pool:add_peer(LocalPort, Ref) of 144 | PeerID -> 145 | Peer = #enet_peer{ 146 | handshake_flow = local, 147 | peer_id = PeerID, 148 | ip = IP, 149 | port = Port, 150 | name = Ref, 151 | host = self(), 152 | channels = Channels, 153 | connect_fun = ConnectFun 154 | }, 155 | start_peer(Peer) 156 | catch 157 | error:pool_full -> {error, reached_peer_limit}; 158 | error:exists -> {error, exists} 159 | end, 160 | {reply, Reply, S}; 161 | 162 | handle_call({send_outgoing_commands, Commands, IP, Port, ID}, _From, S) -> 163 | %% 164 | %% Received outgoing commands from a peer. 165 | %% 166 | %% - Compress commands if compressor available (TODO) 167 | %% - Wrap the commands in a protocol header 168 | %% - Send the packet 169 | %% - Return sent time 170 | %% 171 | SentTime = get_time(), 172 | PH = #protocol_header{ 173 | peer_id = ID, 174 | sent_time = SentTime 175 | }, 176 | Packet = [enet_protocol_encode:protocol_header(PH), Commands], 177 | ok = gen_udp:send(S#state.socket, IP, Port, Packet), 178 | {reply, {sent_time, SentTime}, S}. 179 | 180 | 181 | %%% 182 | %%% handle_cast 183 | %%% 184 | 185 | handle_cast({give_socket, Socket}, S) -> 186 | ok = inet:setopts(Socket, [{active, true}]), 187 | {noreply, S#state{ socket = Socket }}; 188 | 189 | handle_cast(_Msg, State) -> 190 | {noreply, State}. 191 | 192 | 193 | %%% 194 | %%% handle_info 195 | %%% 196 | 197 | handle_info({udp, Socket, IP, Port, Packet}, S) -> 198 | %% 199 | %% Received a UDP packet. 200 | %% 201 | %% - Unpack the ENet protocol header 202 | %% - Decompress the remaining packet if necessary 203 | %% - Send the packet to the peer (ID in protocol header) 204 | %% 205 | #state{ 206 | socket = Socket, 207 | decompress_fun = Decompress, 208 | connect_fun = ConnectFun 209 | } = S, 210 | %% TODO: Replace call to enet_protocol_decode with binary pattern match. 211 | {ok, 212 | #protocol_header{ 213 | compressed = IsCompressed, 214 | peer_id = RecipientPeerID, 215 | sent_time = SentTime 216 | }, 217 | Rest} = enet_protocol_decode:protocol_header(Packet), 218 | Commands = 219 | case IsCompressed of 220 | 0 -> Rest; 221 | 1 -> Decompress(Rest) 222 | end, 223 | LocalPort = get_port(self()), 224 | case RecipientPeerID of 225 | ?NULL_PEER_ID -> 226 | %% No particular peer is the receiver of this packet. 227 | %% Create a new peer. 228 | Ref = make_ref(), 229 | try enet_pool:add_peer(LocalPort, Ref) of 230 | PeerID -> 231 | Peer = #enet_peer{ 232 | handshake_flow = remote, 233 | peer_id = PeerID, 234 | ip = IP, 235 | port = Port, 236 | name = Ref, 237 | host = self(), 238 | connect_fun = ConnectFun 239 | }, 240 | {ok, Pid} = start_peer(Peer), 241 | enet_peer:recv_incoming_packet(Pid, IP, SentTime, Commands) 242 | catch 243 | error:pool_full -> {error, reached_peer_limit}; 244 | error:exists -> {error, exists} 245 | end; 246 | PeerID -> 247 | case enet_pool:pick_peer(LocalPort, PeerID) of 248 | false -> ok; %% Unknown peer - drop the packet 249 | Pid -> 250 | enet_peer:recv_incoming_packet(Pid, IP, SentTime, Commands) 251 | end 252 | end, 253 | {noreply, S}; 254 | 255 | handle_info({gproc, unreg, _Ref, {n, l, {enet_peer, Ref}}}, S) -> 256 | %% 257 | %% A Peer process has exited. 258 | %% 259 | %% - Remove it from the pool 260 | %% 261 | LocalPort = get_port(self()), 262 | true = enet_pool:remove_peer(LocalPort, Ref), 263 | {noreply, S}. 264 | 265 | 266 | %%% 267 | %%% terminate 268 | %%% 269 | 270 | terminate(_Reason, S) -> 271 | ok = gen_udp:close(S#state.socket). 272 | 273 | 274 | %%% 275 | %%% code_change 276 | %%% 277 | 278 | code_change(_OldVsn, State, _Extra) -> 279 | {ok, State}. 280 | 281 | 282 | %%%=================================================================== 283 | %%% Internal functions 284 | %%%=================================================================== 285 | 286 | get_time() -> 287 | erlang:system_time(1000) band 16#FFFF. 288 | 289 | start_peer(Peer = #enet_peer{ name = Ref }) -> 290 | LocalPort = gproc:get_value({p, l, port}, self()), 291 | PeerSup = gproc:where({n, l, {enet_peer_sup, LocalPort}}), 292 | {ok, Pid} = enet_peer_sup:start_peer(PeerSup, Peer), 293 | _Ref = gproc:monitor({n, l, {enet_peer, Ref}}), 294 | {ok, Pid}. 295 | -------------------------------------------------------------------------------- /src/enet_host_sup.erl: -------------------------------------------------------------------------------- 1 | -module(enet_host_sup). 2 | -behaviour(supervisor). 3 | 4 | %% API 5 | -export([ 6 | start_link/3 7 | ]). 8 | 9 | %% Supervisor callbacks 10 | -export([ init/1 ]). 11 | 12 | 13 | %%%=================================================================== 14 | %%% API functions 15 | %%%=================================================================== 16 | 17 | start_link(Port, ConnectFun, Options) -> 18 | supervisor:start_link(?MODULE, [Port, ConnectFun, Options]). 19 | 20 | 21 | %%%=================================================================== 22 | %%% Supervisor callbacks 23 | %%%=================================================================== 24 | 25 | init([Port, ConnectFun, Options]) -> 26 | SupFlags = #{ 27 | strategy => one_for_one, 28 | intensity => 1, 29 | period => 5 30 | }, 31 | PeerLimit = 32 | case lists:keyfind(peer_limit, 1, Options) of 33 | {peer_limit, PLimit} -> PLimit; 34 | false -> 1 35 | end, 36 | Pool = #{ 37 | id => enet_pool, 38 | start => { 39 | enet_pool, 40 | start_link, 41 | [Port, PeerLimit] 42 | }, 43 | restart => permanent, 44 | shutdown => 2000, 45 | type => worker, 46 | modules => [enet_pool] 47 | }, 48 | Host = #{ 49 | id => enet_host, 50 | start => { 51 | enet_host, 52 | start_link, 53 | [Port, ConnectFun, Options] 54 | }, 55 | restart => permanent, 56 | shutdown => 2000, 57 | type => worker, 58 | modules => [enet_host] 59 | }, 60 | Disconnector = #{ 61 | id => enet_disconnector, 62 | start => { 63 | enet_disconnector, 64 | start_link, 65 | [Port] 66 | }, 67 | restart => permanent, 68 | shutdown => 1000, 69 | type => worker, 70 | modules => [enet_disconnector] 71 | }, 72 | PeerSup = #{ 73 | id => enet_peer_sup, 74 | start => { 75 | enet_peer_sup, 76 | start_link, 77 | [Port] 78 | }, 79 | restart => permanent, 80 | shutdown => infinity, 81 | type => supervisor, 82 | modules => [enet_peer_sup] 83 | }, 84 | {ok, {SupFlags, [Pool, Host, Disconnector, PeerSup]}}. 85 | 86 | 87 | %%%=================================================================== 88 | %%% Internal functions 89 | %%%=================================================================== 90 | -------------------------------------------------------------------------------- /src/enet_peer.erl: -------------------------------------------------------------------------------- 1 | -module(enet_peer). 2 | -behaviour(gen_statem). 3 | 4 | -include("enet_peer.hrl"). 5 | -include("enet_commands.hrl"). 6 | -include("enet_protocol.hrl"). 7 | 8 | %% API 9 | -export([ 10 | start_link/2, 11 | disconnect/1, 12 | disconnect_now/1, 13 | channels/1, 14 | channel/2, 15 | recv_incoming_packet/4, 16 | send_command/2, 17 | get_connect_id/1, 18 | get_mtu/1, 19 | get_name/1, 20 | get_peer_id/1, 21 | get_pool/1, 22 | get_pool_worker_id/1 23 | ]). 24 | 25 | %% gen_statem callbacks 26 | -export([ 27 | init/1, 28 | callback_mode/0, 29 | terminate/3, 30 | code_change/4 31 | ]). 32 | 33 | %% gen_statem state functions 34 | -export([ 35 | connecting/3, 36 | acknowledging_connect/3, 37 | acknowledging_verify_connect/3, 38 | verifying_connect/3, 39 | connected/3, 40 | disconnecting/3 41 | ]). 42 | 43 | -record(state, 44 | { 45 | local_port, 46 | ip, 47 | port, 48 | remote_peer_id = undefined, 49 | peer_id, 50 | incoming_session_id = 16#FF, 51 | outgoing_session_id = 16#FF, 52 | incoming_bandwidth = 0, 53 | outgoing_bandwidth = 0, 54 | window_size = ?MAX_WINDOW_SIZE, 55 | packet_throttle_interval = ?PEER_PACKET_THROTTLE_INTERVAL, 56 | packet_throttle_acceleration = ?PEER_PACKET_THROTTLE_ACCELERATION, 57 | packet_throttle_deceleration = ?PEER_PACKET_THROTTLE_DECELERATION, 58 | outgoing_reliable_sequence_number = 1, 59 | incoming_unsequenced_group = 0, 60 | outgoing_unsequenced_group = 1, 61 | unsequenced_window = 0, 62 | connect_id, 63 | host, 64 | channel_count, 65 | channels, 66 | worker, 67 | connect_fun 68 | }). 69 | 70 | 71 | %%============================================================== 72 | %% Connection handshake 73 | %%============================================================== 74 | %% 75 | %% 76 | %% state client server state 77 | %% (init) * 78 | %% | connect 79 | %% |------------------->* (init) 80 | %% 'connecting' | | 'acknowledging connect' 81 | %% | ack connect | 82 | %% |<-------------------| 83 | %% 'acknowledging | | 84 | %% verify connect' | | 85 | %% | verify connect | 86 | %% |<-------------------| 87 | %% | | 'verifying connect' 88 | %% | ack verify connect | 89 | %% |------------------->| 90 | %% 'connected' | | 'connected' 91 | %% | | 92 | %% 93 | %% 94 | %%============================================================== 95 | 96 | %%============================================================== 97 | %% Disconnect procedure 98 | %%============================================================== 99 | %% 100 | %% 101 | %% state client server state 102 | %% peer peer 103 | %% | | 104 | %% 'connected' | | 'connected' 105 | %% | disconnect | 106 | %% |------------------->| 107 | %% 'disconnecting' | | 108 | %% | ack disconnect | 109 | %% |<-------------------| 110 | %% (exit) | | (exit) 111 | %% * * 112 | %% 113 | %% 114 | %%============================================================== 115 | 116 | 117 | %%%=================================================================== 118 | %%% API 119 | %%%=================================================================== 120 | 121 | start_link(LocalPort, Peer) -> 122 | gen_statem:start_link(?MODULE, [LocalPort, Peer], []). 123 | 124 | disconnect(Peer) -> 125 | gen_statem:cast(Peer, disconnect). 126 | 127 | disconnect_now(Peer) -> 128 | gen_statem:cast(Peer, disconnect_now). 129 | 130 | channels(Peer) -> 131 | gen_statem:call(Peer, channels). 132 | 133 | channel(Peer, ID) -> 134 | gen_statem:call(Peer, {channel, ID}). 135 | 136 | recv_incoming_packet(Peer, FromIP, SentTime, Packet) -> 137 | gen_statem:cast(Peer, {incoming_packet, FromIP, SentTime, Packet}). 138 | 139 | send_command(Peer, {H, C}) -> 140 | gen_statem:cast(Peer, {outgoing_command, {H, C}}). 141 | 142 | get_connect_id(Peer) -> 143 | gproc:get_value({p, l, connect_id}, Peer). 144 | 145 | get_mtu(Peer) -> 146 | gproc:get_value({p, l, mtu}, Peer). 147 | 148 | get_name(Peer) -> 149 | gproc:get_value({p, l, name}, Peer). 150 | 151 | get_peer_id(Peer) -> 152 | gproc:get_value({p, l, peer_id}, Peer). 153 | 154 | get_pool(Peer) -> 155 | gen_statem:call(Peer, pool). 156 | 157 | get_pool_worker_id(Peer) -> 158 | gen_statem:call(Peer, pool_worker_id). 159 | 160 | 161 | %%%=================================================================== 162 | %%% gen_statem callbacks 163 | %%%=================================================================== 164 | 165 | init([LocalPort, P = #enet_peer{ handshake_flow = local }]) -> 166 | %% 167 | %% The client application wants to connect to a remote peer. 168 | %% 169 | %% - Send a Connect command to the remote peer (use peer ID) 170 | %% - Start in the 'connecting' state 171 | %% 172 | #enet_peer{ 173 | peer_id = PeerID, 174 | ip = IP, 175 | port = Port, 176 | name = Ref, 177 | host = Host, 178 | channels = N, 179 | connect_fun = ConnectFun 180 | } = P, 181 | enet_pool:connect_peer(LocalPort, Ref), 182 | gproc:reg({n, l, {enet_peer, Ref}}), 183 | gproc:reg({p, l, name}, Ref), 184 | gproc:reg({p, l, peer_id}, PeerID), 185 | S = #state{ 186 | host = Host, 187 | local_port = LocalPort, 188 | ip = IP, 189 | port = Port, 190 | peer_id = PeerID, 191 | channel_count = N, 192 | connect_fun = ConnectFun 193 | }, 194 | {ok, connecting, S}; 195 | 196 | init([LocalPort, P = #enet_peer{ handshake_flow = remote }]) -> 197 | %% 198 | %% A remote peer wants to connect to the client application. 199 | %% 200 | %% - Start in the 'acknowledging_connect' state 201 | %% - Handle the received Connect command 202 | %% 203 | #enet_peer{ 204 | peer_id = PeerID, 205 | ip = IP, 206 | port = Port, 207 | name = Ref, 208 | host = Host, 209 | connect_fun = ConnectFun 210 | } = P, 211 | enet_pool:connect_peer(LocalPort, Ref), 212 | gproc:reg({n, l, {enet_peer, Ref}}), 213 | gproc:reg({p, l, name}, Ref), 214 | gproc:reg({p, l, peer_id}, PeerID), 215 | S = #state{ 216 | host = Host, 217 | local_port = LocalPort, 218 | ip = IP, 219 | port = Port, 220 | peer_id = PeerID, 221 | connect_fun = ConnectFun 222 | }, 223 | {ok, acknowledging_connect, S}. 224 | 225 | 226 | callback_mode() -> 227 | [state_functions, state_enter]. 228 | 229 | 230 | %%% 231 | %%% Connecting state 232 | %%% 233 | 234 | connecting(enter, _OldState, S) -> 235 | %% 236 | %% Sending the initial Connect command. 237 | %% 238 | #state{ 239 | host = Host, 240 | channel_count = ChannelCount, 241 | ip = IP, 242 | port = Port, 243 | peer_id = PeerID, 244 | incoming_session_id = IncomingSessionID, 245 | outgoing_session_id = OutgoingSessionID, 246 | packet_throttle_interval = PacketThrottleInterval, 247 | packet_throttle_acceleration = PacketThrottleAcceleration, 248 | packet_throttle_deceleration = PacketThrottleDeceleration, 249 | outgoing_reliable_sequence_number = SequenceNr 250 | } = S, 251 | IncomingBandwidth = enet_host:get_incoming_bandwidth(Host), 252 | OutgoingBandwidth = enet_host:get_outgoing_bandwidth(Host), 253 | MTU = enet_host:get_mtu(Host), 254 | gproc:reg({p, l, mtu}, MTU), 255 | <> = crypto:strong_rand_bytes(4), 256 | {ConnectH, ConnectC} = 257 | enet_command:connect( 258 | PeerID, 259 | IncomingSessionID, 260 | OutgoingSessionID, 261 | ChannelCount, 262 | MTU, 263 | IncomingBandwidth, 264 | OutgoingBandwidth, 265 | PacketThrottleInterval, 266 | PacketThrottleAcceleration, 267 | PacketThrottleDeceleration, 268 | ConnectID, 269 | SequenceNr), 270 | HBin = enet_protocol_encode:command_header(ConnectH), 271 | CBin = enet_protocol_encode:command(ConnectC), 272 | Data = [HBin, CBin], 273 | {sent_time, SentTime} = 274 | enet_host:send_outgoing_commands(Host, Data, IP, Port), 275 | ChannelID = 16#FF, 276 | ConnectTimeout = 277 | make_resend_timer( 278 | ChannelID, SentTime, SequenceNr, ?PEER_TIMEOUT_MINIMUM, Data), 279 | NewS = S#state{ 280 | outgoing_reliable_sequence_number = SequenceNr + 1, 281 | connect_id = ConnectID 282 | }, 283 | {keep_state, NewS, [ConnectTimeout]}; 284 | 285 | connecting(cast, {incoming_command, {H, C = #acknowledge{}}}, S) -> 286 | %% 287 | %% Received an Acknowledge command in the 'connecting' state. 288 | %% 289 | %% - Verify that the acknowledge is correct 290 | %% - Change state to 'acknowledging_verify_connect' 291 | %% 292 | #command_header{ channel_id = ChannelID } = H, 293 | #acknowledge{ 294 | received_reliable_sequence_number = SequenceNumber, 295 | received_sent_time = SentTime 296 | } = C, 297 | CanceledTimeout = cancel_resend_timer(ChannelID, SentTime, SequenceNumber), 298 | {next_state, acknowledging_verify_connect, S, [CanceledTimeout]}; 299 | 300 | connecting({timeout, {_ChannelID, _SentTime, _SequenceNumber}}, _, S) -> 301 | {stop, timeout, S}; 302 | 303 | connecting(EventType, EventContent, S) -> 304 | handle_event(EventType, EventContent, S). 305 | 306 | 307 | %%% 308 | %%% Acknowledging Connect state 309 | %%% 310 | 311 | acknowledging_connect(enter, _OldState, S) -> 312 | {keep_state, S}; 313 | 314 | acknowledging_connect(cast, {incoming_command, {_H, C = #connect{}}}, S) -> 315 | %% 316 | %% Received a Connect command. 317 | %% 318 | %% - Verify that the data is sane (TODO) 319 | %% - Send a VerifyConnect command (use peer ID) 320 | %% - Start in the 'verifying_connect' state 321 | %% 322 | #connect{ 323 | outgoing_peer_id = RemotePeerID, 324 | incoming_session_id = _IncomingSessionID, 325 | outgoing_session_id = _OutgoingSessionID, 326 | mtu = MTU, 327 | window_size = WindowSize, 328 | channel_count = ChannelCount, 329 | incoming_bandwidth = IncomingBandwidth, 330 | outgoing_bandwidth = OutgoingBandwidth, 331 | packet_throttle_interval = PacketThrottleInterval, 332 | packet_throttle_acceleration = PacketThrottleAcceleration, 333 | packet_throttle_deceleration = PacketThrottleDeceleration, 334 | connect_id = ConnectID, 335 | data = _Data 336 | } = C, 337 | #state{ 338 | host = Host, 339 | ip = IP, 340 | port = Port, 341 | peer_id = PeerID, 342 | incoming_session_id = IncomingSessionID, 343 | outgoing_session_id = OutgoingSessionID, 344 | outgoing_reliable_sequence_number = SequenceNr 345 | } = S, 346 | gproc:reg({p, l, mtu}, MTU), 347 | HostChannelLimit = enet_host:get_channel_limit(Host), 348 | HostIncomingBandwidth = enet_host:get_incoming_bandwidth(Host), 349 | HostOutgoingBandwidth = enet_host:get_outgoing_bandwidth(Host), 350 | {VCH, VCC} = enet_command:verify_connect(C, 351 | PeerID, 352 | IncomingSessionID, 353 | OutgoingSessionID, 354 | HostChannelLimit, 355 | HostIncomingBandwidth, 356 | HostOutgoingBandwidth, 357 | SequenceNr), 358 | HBin = enet_protocol_encode:command_header(VCH), 359 | CBin = enet_protocol_encode:command(VCC), 360 | Data = [HBin, CBin], 361 | {sent_time, SentTime} = 362 | enet_host:send_outgoing_commands(Host, Data, IP, Port, RemotePeerID), 363 | ChannelID = 16#FF, 364 | VerifyConnectTimeout = 365 | make_resend_timer( 366 | ChannelID, SentTime, SequenceNr, ?PEER_TIMEOUT_MINIMUM, Data), 367 | NewS = S#state{ 368 | remote_peer_id = RemotePeerID, 369 | %% channels = Channels, 370 | connect_id = ConnectID, 371 | incoming_bandwidth = IncomingBandwidth, 372 | outgoing_bandwidth = OutgoingBandwidth, 373 | window_size = WindowSize, 374 | packet_throttle_interval = PacketThrottleInterval, 375 | packet_throttle_acceleration = PacketThrottleAcceleration, 376 | packet_throttle_deceleration = PacketThrottleDeceleration, 377 | outgoing_reliable_sequence_number = SequenceNr + 1, 378 | channel_count = ChannelCount 379 | }, 380 | {next_state, verifying_connect, NewS, [VerifyConnectTimeout]}; 381 | 382 | acknowledging_connect({timeout, {_ChannelID, _SentTime, _SequenceNr}}, _, S) -> 383 | {stop, timeout, S}; 384 | 385 | acknowledging_connect(EventType, EventContent, S) -> 386 | handle_event(EventType, EventContent, S). 387 | 388 | 389 | %%% 390 | %%% Acknowledging Verify Connect state 391 | %%% 392 | 393 | acknowledging_verify_connect(enter, _OldState, S) -> 394 | {keep_state, S}; 395 | 396 | acknowledging_verify_connect( 397 | cast, {incoming_command, {_H, C = #verify_connect{}}}, S) -> 398 | %% 399 | %% Received a Verify Connect command in the 'acknowledging_verify_connect' 400 | %% state. 401 | %% 402 | %% - Verify that the data is correct 403 | %% - Add the remote peer ID to the Peer Table 404 | %% - Notify worker that we are connected 405 | %% - Change state to 'connected' 406 | %% 407 | #verify_connect{ 408 | outgoing_peer_id = RemotePeerID, 409 | incoming_session_id = _IncomingSessionID, 410 | outgoing_session_id = _OutgoingSessionID, 411 | mtu = RemoteMTU, 412 | window_size = WindowSize, 413 | channel_count = RemoteChannelCount, 414 | incoming_bandwidth = IncomingBandwidth, 415 | outgoing_bandwidth = OutgoingBandwidth, 416 | packet_throttle_interval = ThrottleInterval, 417 | packet_throttle_acceleration = ThrottleAcceleration, 418 | packet_throttle_deceleration = ThrottleDeceleration, 419 | connect_id = ConnectID 420 | } = C, 421 | %% 422 | %% TODO: Calculate and validate Session IDs 423 | %% 424 | #state{ channel_count = LocalChannelCount } = S, 425 | LocalMTU = get_mtu(self()), 426 | case S of 427 | #state{ 428 | %% --- 429 | %% Fields below are matched against the values received in 430 | %% the Verify Connect command. 431 | %% --- 432 | window_size = WindowSize, 433 | incoming_bandwidth = IncomingBandwidth, 434 | outgoing_bandwidth = OutgoingBandwidth, 435 | packet_throttle_interval = ThrottleInterval, 436 | packet_throttle_acceleration = ThrottleAcceleration, 437 | packet_throttle_deceleration = ThrottleDeceleration, 438 | connect_id = ConnectID 439 | %% --- 440 | } when 441 | LocalChannelCount =:= RemoteChannelCount, 442 | LocalMTU =:= RemoteMTU -> 443 | NewS = S#state{ remote_peer_id = RemotePeerID }, 444 | {next_state, connected, NewS}; 445 | _Mismatch -> 446 | {stop, connect_verification_failed, S} 447 | end; 448 | 449 | acknowledging_verify_connect(EventType, EventContent, S) -> 450 | handle_event(EventType, EventContent, S). 451 | 452 | 453 | %%% 454 | %%% Verifying Connect state 455 | %%% 456 | 457 | verifying_connect(enter, _OldState, S) -> 458 | {keep_state, S}; 459 | 460 | verifying_connect(cast, {incoming_command, {H, C = #acknowledge{}}}, S) -> 461 | %% 462 | %% Received an Acknowledge command in the 'verifying_connect' state. 463 | %% 464 | %% - Verify that the acknowledge is correct 465 | %% - Notify worker that a new peer has been connected 466 | %% - Change to 'connected' state 467 | %% 468 | #command_header{ channel_id = ChannelID } = H, 469 | #acknowledge{ 470 | received_reliable_sequence_number = SequenceNumber, 471 | received_sent_time = SentTime 472 | } = C, 473 | CanceledTimeout = cancel_resend_timer(ChannelID, SentTime, SequenceNumber), 474 | {next_state, connected, S, [CanceledTimeout]}; 475 | 476 | verifying_connect(EventType, EventContent, S) -> 477 | handle_event(EventType, EventContent, S). 478 | 479 | 480 | %%% 481 | %%% Connected state 482 | %%% 483 | 484 | connected(enter, _OldState, S) -> 485 | #state{ 486 | local_port = LocalPort, 487 | ip = IP, 488 | port = Port, 489 | remote_peer_id = RemotePeerID, 490 | connect_id = ConnectID, 491 | channel_count = N, 492 | connect_fun = ConnectFun 493 | } = S, 494 | true = gproc:mreg(p, l, [ 495 | {connect_id, ConnectID}, 496 | {remote_host_port, Port}, 497 | {remote_peer_id, RemotePeerID} 498 | ]), 499 | ok = enet_disconnector:set_trigger(LocalPort, RemotePeerID, IP, Port), 500 | Channels = start_channels(N), 501 | PeerInfo = #{ 502 | ip => IP, 503 | port => Port, 504 | peer => self(), 505 | channels => Channels, 506 | connect_id => ConnectID 507 | }, 508 | case start_worker(ConnectFun, PeerInfo) of 509 | {error, Reason} -> 510 | {stop, {worker_init_error, Reason}, S}; 511 | {ok, Worker} -> 512 | _Ref = monitor(process, Worker), 513 | [enet_channel:set_worker(C, Worker) || C <- maps:values(Channels)], 514 | NewS = S#state{ 515 | channels = Channels, 516 | worker = Worker 517 | }, 518 | SendTimeout = reset_send_timer(), 519 | RecvTimeout = reset_recv_timer(), 520 | {keep_state, NewS, [SendTimeout, RecvTimeout]} 521 | end; 522 | 523 | connected(cast, {incoming_command, {_H, #ping{}}}, S) -> 524 | %% 525 | %% Received PING. 526 | %% 527 | %% - Reset the receive-timer 528 | %% 529 | RecvTimeout = reset_recv_timer(), 530 | {keep_state, S, [RecvTimeout]}; 531 | 532 | connected(cast, {incoming_command, {H, C = #acknowledge{}}}, S) -> 533 | %% 534 | %% Received an Acknowledge command. 535 | %% 536 | %% - Verify that the acknowledge is correct 537 | %% - Reset the receive-timer 538 | %% 539 | #command_header{ channel_id = ChannelID } = H, 540 | #acknowledge{ 541 | received_reliable_sequence_number = SequenceNumber, 542 | received_sent_time = SentTime 543 | } = C, 544 | CanceledTimeout = cancel_resend_timer(ChannelID, SentTime, SequenceNumber), 545 | RecvTimeout = reset_recv_timer(), 546 | {keep_state, S, [CanceledTimeout, RecvTimeout]}; 547 | 548 | connected(cast, {incoming_command, {_H, C = #bandwidth_limit{}}}, S) -> 549 | %% 550 | %% Received Bandwidth Limit command. 551 | %% 552 | %% - Set bandwidth limit 553 | %% - Reset the receive-timer 554 | %% 555 | #bandwidth_limit{ 556 | incoming_bandwidth = IncomingBandwidth, 557 | outgoing_bandwidth = OutgoingBandwidth 558 | } = C, 559 | #state{ host = Host } = S, 560 | HostOutgoingBandwidth = enet_host:get_outgoing_bandwidth(Host), 561 | WSize = 562 | case {IncomingBandwidth, HostOutgoingBandwidth} of 563 | {0, 0} -> ?MAX_WINDOW_SIZE; 564 | {0, H} -> ?MIN_WINDOW_SIZE * H div ?PEER_WINDOW_SIZE_SCALE; 565 | {P, 0} -> ?MIN_WINDOW_SIZE * P div ?PEER_WINDOW_SIZE_SCALE; 566 | {P, H} -> ?MIN_WINDOW_SIZE * min(P, H) div ?PEER_WINDOW_SIZE_SCALE 567 | end, 568 | NewS = S#state{ 569 | incoming_bandwidth = IncomingBandwidth, 570 | outgoing_bandwidth = OutgoingBandwidth, 571 | window_size = max(?MIN_WINDOW_SIZE, min(?MAX_WINDOW_SIZE, WSize)) 572 | }, 573 | RecvTimeout = reset_recv_timer(), 574 | {keep_state, NewS, [RecvTimeout]}; 575 | 576 | connected(cast, {incoming_command, {_H, C = #throttle_configure{}}}, S) -> 577 | %% 578 | %% Received Throttle Configure command. 579 | %% 580 | %% - Set throttle configuration 581 | %% - Reset the receive-timer 582 | %% 583 | #throttle_configure{ 584 | packet_throttle_interval = Interval, 585 | packet_throttle_acceleration = Acceleration, 586 | packet_throttle_deceleration = Deceleration 587 | } = C, 588 | NewS = S#state{ 589 | packet_throttle_interval = Interval, 590 | packet_throttle_acceleration = Acceleration, 591 | packet_throttle_deceleration = Deceleration 592 | }, 593 | RecvTimeout = reset_recv_timer(), 594 | {keep_state, NewS, [RecvTimeout]}; 595 | 596 | connected(cast, {incoming_command, {H, C = #unsequenced{}}}, S) -> 597 | %% 598 | %% Received Send Unsequenced command. 599 | %% 600 | %% - Forward the command to the relevant channel 601 | %% - Reset the receive-timer 602 | %% 603 | #command_header{ channel_id = ChannelID } = H, 604 | #state{ channels = #{ ChannelID := Channel } } = S, 605 | ok = enet_channel:recv_unsequenced(Channel, {H, C}), 606 | RecvTimeout = reset_recv_timer(), 607 | {keep_state, S, [RecvTimeout]}; 608 | 609 | connected(cast, {incoming_command, {H, C = #unreliable{}}}, S) -> 610 | %% 611 | %% Received Send Unreliable command. 612 | %% 613 | %% - Forward the command to the relevant channel 614 | %% - Reset the receive-timer 615 | %% 616 | #command_header{ channel_id = ChannelID } = H, 617 | #state{ channels = #{ ChannelID := Channel } } = S, 618 | ok = enet_channel:recv_unreliable(Channel, {H, C}), 619 | RecvTimeout = reset_recv_timer(), 620 | {keep_state, S, [RecvTimeout]}; 621 | 622 | connected(cast, {incoming_command, {H, C = #reliable{}}}, S) -> 623 | %% 624 | %% Received Send Reliable command. 625 | %% 626 | %% - Forward the command to the relevant channel 627 | %% - Reset the receive-timer 628 | %% 629 | #command_header{ channel_id = ChannelID } = H, 630 | #state{ channels = #{ ChannelID := Channel } } = S, 631 | ok = enet_channel:recv_reliable(Channel, {H, C}), 632 | RecvTimeout = reset_recv_timer(), 633 | {keep_state, S, [RecvTimeout]}; 634 | 635 | connected(cast, {incoming_command, {_H, #disconnect{}}}, S) -> 636 | %% 637 | %% Received Disconnect command. 638 | %% 639 | %% - Notify worker application 640 | %% - Stop 641 | %% 642 | #state{ 643 | worker = Worker, 644 | local_port = LocalPort, 645 | ip = IP, 646 | port = Port, 647 | remote_peer_id = RemotePeerID, 648 | connect_id = ConnectID 649 | } = S, 650 | ok = enet_disconnector:unset_trigger(LocalPort, RemotePeerID, IP, Port), 651 | Worker ! {enet, disconnected, remote, self(), ConnectID}, 652 | {stop, normal, S}; 653 | 654 | connected(cast, {outgoing_command, {H, C = #unsequenced{}}}, S) -> 655 | %% 656 | %% Sending an Unsequenced, unreliable command. 657 | %% 658 | %% - TODO: Increment total data passed through peer 659 | %% - Increment outgoing_unsequenced_group 660 | %% - Set unsequenced_group on command to outgoing_unsequenced_group 661 | %% - Queue the command for sending 662 | %% - Reset the send-timer 663 | %% 664 | #state{ 665 | host = Host, 666 | ip = IP, 667 | port = Port, 668 | remote_peer_id = RemotePeerID, 669 | outgoing_unsequenced_group = Group 670 | } = S, 671 | C1 = C#unsequenced{ group = Group }, 672 | HBin = enet_protocol_encode:command_header(H), 673 | CBin = enet_protocol_encode:command(C1), 674 | Data = [HBin, CBin], 675 | {sent_time, _SentTime} = 676 | enet_host:send_outgoing_commands(Host, Data, IP, Port, RemotePeerID), 677 | NewS = S#state{ outgoing_unsequenced_group = Group + 1 }, 678 | SendTimeout = reset_send_timer(), 679 | {keep_state, NewS, [SendTimeout]}; 680 | 681 | connected(cast, {outgoing_command, {H, C = #unreliable{}}}, S) -> 682 | %% 683 | %% Sending a Sequenced, unreliable command. 684 | %% 685 | %% - Forward the encoded command to the host 686 | %% - Reset the send-timer 687 | %% 688 | #state{ 689 | host = Host, 690 | ip = IP, 691 | port = Port, 692 | remote_peer_id = RemotePeerID 693 | } = S, 694 | HBin = enet_protocol_encode:command_header(H), 695 | CBin = enet_protocol_encode:command(C), 696 | Data = [HBin, CBin], 697 | {sent_time, _SentTime} = 698 | enet_host:send_outgoing_commands(Host, Data, IP, Port, RemotePeerID), 699 | SendTimeout = reset_send_timer(), 700 | {keep_state, S, [SendTimeout]}; 701 | 702 | connected(cast, {outgoing_command, {H, C = #reliable{}}}, S) -> 703 | %% 704 | %% Sending a Sequenced, reliable command. 705 | %% 706 | %% - Forward the encoded command to the host 707 | %% - Reset the send-timer 708 | %% 709 | #state{ 710 | host = Host, 711 | ip = IP, 712 | port = Port, 713 | remote_peer_id = RemotePeerID 714 | } = S, 715 | #command_header{ 716 | channel_id = ChannelID, 717 | reliable_sequence_number = SequenceNr 718 | } = H, 719 | HBin = enet_protocol_encode:command_header(H), 720 | CBin = enet_protocol_encode:command(C), 721 | Data = [HBin, CBin], 722 | {sent_time, SentTime} = 723 | enet_host:send_outgoing_commands(Host, Data, IP, Port, RemotePeerID), 724 | SendReliableTimeout = 725 | make_resend_timer( 726 | ChannelID, SentTime, SequenceNr, ?PEER_TIMEOUT_MINIMUM, Data), 727 | SendTimeout = reset_send_timer(), 728 | {keep_state, S, [SendReliableTimeout, SendTimeout]}; 729 | 730 | connected(cast, disconnect, State) -> 731 | %% 732 | %% Disconnecting. 733 | %% 734 | %% - Queue a Disconnect command for sending 735 | %% - Change state to 'disconnecting' 736 | %% 737 | #state{ 738 | host = Host, 739 | ip = IP, 740 | port = Port, 741 | remote_peer_id = RemotePeerID 742 | } = State, 743 | {H, C} = enet_command:sequenced_disconnect(), 744 | HBin = enet_protocol_encode:command_header(H), 745 | CBin = enet_protocol_encode:command(C), 746 | Data = [HBin, CBin], 747 | enet_host:send_outgoing_commands(Host, Data, IP, Port, RemotePeerID), 748 | {next_state, disconnecting, State}; 749 | 750 | connected(cast, disconnect_now, State) -> 751 | %% 752 | %% Disconnecting immediately. 753 | %% 754 | %% - Stop 755 | %% 756 | {stop, normal, State}; 757 | 758 | connected({timeout, {ChannelID, SentTime, SequenceNr}}, Data, S) -> 759 | %% 760 | %% A resend-timer was triggered. 761 | %% 762 | %% - TODO: Keep track of number of resends 763 | %% - Resend the associated command 764 | %% - Reset the resend-timer 765 | %% - Reset the send-timer 766 | %% 767 | #state{ 768 | host = Host, 769 | ip = IP, 770 | port = Port, 771 | remote_peer_id = RemotePeerID 772 | } = S, 773 | enet_host:send_outgoing_commands(Host, Data, IP, Port, RemotePeerID), 774 | NewTimeout = 775 | make_resend_timer( 776 | ChannelID, SentTime, SequenceNr, ?PEER_TIMEOUT_MINIMUM, Data), 777 | SendTimeout = reset_send_timer(), 778 | {keep_state, S, [NewTimeout, SendTimeout]}; 779 | 780 | connected({timeout, recv}, ping, S) -> 781 | %% 782 | %% The receive-timer was triggered. 783 | %% 784 | %% - Stop 785 | %% 786 | {stop, timeout, S}; 787 | 788 | connected({timeout, send}, ping, S) -> 789 | %% 790 | %% The send-timer was triggered. 791 | %% 792 | %% - Send a PING 793 | %% - Reset the send-timer 794 | %% 795 | #state{ 796 | host = Host, 797 | ip = IP, 798 | port = Port, 799 | remote_peer_id = RemotePeerID 800 | } = S, 801 | {H, C} = enet_command:ping(), 802 | HBin = enet_protocol_encode:command_header(H), 803 | CBin = enet_protocol_encode:command(C), 804 | Data = [HBin, CBin], 805 | {sent_time, _SentTime} = 806 | enet_host:send_outgoing_commands(Host, Data, IP, Port, RemotePeerID), 807 | SendTimeout = reset_send_timer(), 808 | {keep_state, S, [SendTimeout]}; 809 | 810 | connected(EventType, EventContent, S) -> 811 | handle_event(EventType, EventContent, S). 812 | 813 | 814 | %%% 815 | %%% Disconnecting state 816 | %%% 817 | 818 | disconnecting(enter, _OldState, S) -> 819 | #state{ 820 | local_port = LocalPort, 821 | ip = IP, 822 | port = Port, 823 | remote_peer_id = RemotePeerID 824 | } = S, 825 | ok = enet_disconnector:unset_trigger(LocalPort, RemotePeerID, IP, Port), 826 | {keep_state, S}; 827 | 828 | disconnecting(cast, {incoming_command, {_H, _C = #acknowledge{}}}, S) -> 829 | %% 830 | %% Received an Acknowledge command in the 'disconnecting' state. 831 | %% 832 | %% - Verify that the acknowledge is correct 833 | %% - Notify worker application 834 | %% - Stop 835 | %% 836 | #state{ 837 | worker = Worker, 838 | connect_id = ConnectID 839 | } = S, 840 | Worker ! {enet, disconnected, local, self(), ConnectID}, 841 | {stop, normal, S}; 842 | 843 | disconnecting(cast, {incoming_command, {_H, _C}}, S) -> 844 | {keep_state, S}; 845 | 846 | disconnecting(EventType, EventContent, S) -> 847 | handle_event(EventType, EventContent, S). 848 | 849 | 850 | %%% 851 | %%% terminate 852 | %%% 853 | 854 | terminate(_Reason, _StateName, #state{ local_port = LocalPort }) -> 855 | Name = get_name(self()), 856 | enet_pool:disconnect_peer(LocalPort, Name), 857 | ok. 858 | 859 | 860 | %%% 861 | %%% code_change 862 | %%% 863 | 864 | code_change(_OldVsn, StateName, State, _Extra) -> 865 | {ok, StateName, State}. 866 | 867 | 868 | %%%=================================================================== 869 | %%% Internal functions 870 | %%%=================================================================== 871 | 872 | handle_event(cast, {incoming_packet, FromIP, SentTime, Packet}, S) -> 873 | %% 874 | %% Received an incoming packet of commands. 875 | %% 876 | %% - Split and decode the commands from the binary 877 | %% - Send the commands as individual events to ourselves 878 | %% 879 | #state{ host = Host, port = Port } = S, 880 | {ok, Commands} = enet_protocol_decode:commands(Packet), 881 | lists:foreach( 882 | fun ({H = #command_header{ please_acknowledge = 0 }, C}) -> 883 | %% 884 | %% Received command that does not need to be acknowledged. 885 | %% 886 | %% - Send the command to self for handling 887 | %% 888 | gen_statem:cast(self(), {incoming_command, {H, C}}); 889 | ({H = #command_header{ please_acknowledge = 1 }, C}) -> 890 | %% 891 | %% Received a command that should be acknowledged. 892 | %% 893 | %% - Acknowledge the command 894 | %% - Send the command to self for handling 895 | %% 896 | {AckH, AckC} = enet_command:acknowledge(H, SentTime), 897 | HBin = enet_protocol_encode:command_header(AckH), 898 | CBin = enet_protocol_encode:command(AckC), 899 | RemotePeerID = 900 | case C of 901 | #connect{} -> C#connect.outgoing_peer_id; 902 | #verify_connect{} -> C#verify_connect.outgoing_peer_id; 903 | _ -> S#state.remote_peer_id 904 | end, 905 | {sent_time, _AckSentTime} = 906 | enet_host:send_outgoing_commands( 907 | Host, [HBin, CBin], FromIP, Port, RemotePeerID), 908 | gen_statem:cast(self(), {incoming_command, {H, C}}) 909 | end, 910 | Commands), 911 | {keep_state, S#state{ ip = FromIP }}; 912 | 913 | handle_event({call, From}, channels, S) -> 914 | {keep_state, S, [{reply, From, S#state.channels}]}; 915 | 916 | handle_event({call, From}, pool, S) -> 917 | {keep_state, S, [{reply, From, S#state.local_port}]}; 918 | 919 | handle_event({call, From}, pool_worker_id, S) -> 920 | WorkerID = enet_pool:worker_id(S#state.local_port, get_name(self())), 921 | {keep_state, S, [{reply, From, WorkerID}]}; 922 | 923 | handle_event({call, From}, {channel, ID}, S) -> 924 | #state{ channels = #{ ID := Channel }} = S, 925 | {keep_state, S, [{reply, From, Channel}]}; 926 | 927 | handle_event(info, {'DOWN', _, process, O, _}, S = #state{ worker = O }) -> 928 | {stop, worker_process_down, S}. 929 | 930 | 931 | start_channels(N) -> 932 | IDs = lists:seq(0, N-1), 933 | Channels = 934 | lists:map( 935 | fun (ID) -> 936 | {ok, Channel} = enet_channel:start_link(ID, self()), 937 | {ID, Channel} 938 | end, 939 | IDs), 940 | maps:from_list(Channels). 941 | 942 | make_resend_timer(ChannelID, SentTime, SequenceNumber, Time, Data) -> 943 | {{timeout, {ChannelID, SentTime, SequenceNumber}}, Time, Data}. 944 | 945 | cancel_resend_timer(ChannelID, SentTime, SequenceNumber) -> 946 | {{timeout, {ChannelID, SentTime, SequenceNumber}}, infinity, undefined}. 947 | 948 | reset_recv_timer() -> 949 | {{timeout, recv}, 2 * ?PEER_PING_INTERVAL, ping}. 950 | 951 | reset_send_timer() -> 952 | {{timeout, send}, ?PEER_PING_INTERVAL, ping}. 953 | 954 | start_worker({Module, Fun, Args}, PeerInfo) -> 955 | erlang:apply(Module, Fun, Args ++ [PeerInfo]); 956 | start_worker(ConnectFun, PeerInfo) -> 957 | ConnectFun(PeerInfo). 958 | -------------------------------------------------------------------------------- /src/enet_peer.hrl: -------------------------------------------------------------------------------- 1 | -record(enet_peer, 2 | { 3 | handshake_flow, 4 | peer_id, 5 | ip, 6 | port, 7 | name, 8 | host, 9 | channels, 10 | connect_fun 11 | }). 12 | -------------------------------------------------------------------------------- /src/enet_peer_sup.erl: -------------------------------------------------------------------------------- 1 | -module(enet_peer_sup). 2 | -behaviour(supervisor). 3 | 4 | %% API 5 | -export([ 6 | start_link/1, 7 | start_peer/2 8 | ]). 9 | 10 | %% Supervisor callbacks 11 | -export([ init/1 ]). 12 | 13 | 14 | %%%=================================================================== 15 | %%% API functions 16 | %%%=================================================================== 17 | 18 | start_link(Port) -> 19 | supervisor:start_link(?MODULE, [Port]). 20 | 21 | start_peer(Supervisor, Peer) -> 22 | supervisor:start_child(Supervisor, [Peer]). 23 | 24 | 25 | %%%=================================================================== 26 | %%% Supervisor callbacks 27 | %%%=================================================================== 28 | 29 | init([Port]) -> 30 | true = gproc:reg({n, l, {enet_peer_sup, Port}}), 31 | SupFlags = #{ 32 | strategy => simple_one_for_one, 33 | intensity => 1, 34 | period => 3 35 | }, 36 | ChildSpecs = [#{ 37 | id => enet_peer, 38 | start => {enet_peer, start_link, [Port]}, 39 | restart => temporary, 40 | shutdown => brutal_kill, 41 | type => worker, 42 | modules => [enet_peer] 43 | }], 44 | {ok, {SupFlags, ChildSpecs}}. 45 | 46 | 47 | %%%=================================================================== 48 | %%% Internal functions 49 | %%%=================================================================== 50 | -------------------------------------------------------------------------------- /src/enet_pool.erl: -------------------------------------------------------------------------------- 1 | -module(enet_pool). 2 | -behaviour(gen_server). 3 | 4 | %% API 5 | -export([ 6 | start_link/2, 7 | add_peer/2, 8 | pick_peer/2, 9 | remove_peer/2, 10 | connect_peer/2, 11 | disconnect_peer/2, 12 | active_peers/1, 13 | worker_id/2 14 | ]). 15 | 16 | %% gen_server callbacks 17 | -export([ 18 | init/1, 19 | handle_call/3, 20 | handle_cast/2, 21 | handle_info/2, 22 | terminate/2 23 | ]). 24 | 25 | -record(state, 26 | { 27 | port 28 | }). 29 | 30 | 31 | %%%=================================================================== 32 | %%% API 33 | %%%=================================================================== 34 | 35 | start_link(Port, PeerLimit) -> 36 | gen_server:start_link(?MODULE, [Port, PeerLimit], []). 37 | 38 | add_peer(Port, Name) -> 39 | gproc_pool:add_worker(Port, Name). 40 | 41 | pick_peer(Port, PeerID) -> 42 | gproc_pool:pick_worker(Port, PeerID). 43 | 44 | remove_peer(Port, Name) -> 45 | gproc_pool:remove_worker(Port, Name). 46 | 47 | connect_peer(Port, Name) -> 48 | gproc_pool:connect_worker(Port, Name). 49 | 50 | disconnect_peer(Port, Name) -> 51 | gproc_pool:disconnect_worker(Port, Name). 52 | 53 | active_peers(Port) -> 54 | gproc_pool:active_workers(Port). 55 | 56 | worker_id(Port, Name) -> 57 | gproc_pool:worker_id(Port, Name). 58 | 59 | 60 | %%%=================================================================== 61 | %%% gen_server callbacks 62 | %%%=================================================================== 63 | 64 | init([Port, PeerLimit]) -> 65 | process_flag(trap_exit, true), 66 | true = gproc:reg({n, l, {enet_pool, Port}}), 67 | try gproc_pool:new(Port, direct, [{size, PeerLimit}, {auto_size, false}]) of 68 | ok -> ok 69 | catch 70 | error:exists -> ok 71 | end, 72 | {ok, #state{ port = Port }}. 73 | 74 | handle_call(_Request, _From, State) -> 75 | Reply = ok, 76 | {reply, Reply, State}. 77 | 78 | handle_cast(_Request, State) -> 79 | {noreply, State}. 80 | 81 | handle_info(_Info, State) -> 82 | {noreply, State}. 83 | 84 | terminate(_Reason, #state{ port = Port }) -> 85 | gproc_pool:force_delete(Port), 86 | ok. 87 | 88 | 89 | %%%=================================================================== 90 | %%% Internal functions 91 | %%%=================================================================== 92 | -------------------------------------------------------------------------------- /src/enet_protocol.hrl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Command labels 3 | %%% 4 | 5 | -define(COMMAND_ACKNOWLEDGE, 1). 6 | -define(COMMAND_CONNECT, 2). 7 | -define(COMMAND_VERIFY_CONNECT, 3). 8 | -define(COMMAND_DISCONNECT, 4). 9 | -define(COMMAND_PING, 5). 10 | -define(COMMAND_SEND_RELIABLE, 6). 11 | -define(COMMAND_SEND_UNRELIABLE, 7). 12 | -define(COMMAND_SEND_FRAGMENT, 8). 13 | -define(COMMAND_SEND_UNSEQUENCED, 9). 14 | -define(COMMAND_BANDWIDTH_LIMIT, 10). 15 | -define(COMMAND_THROTTLE_CONFIGURE, 11). 16 | -define(COMMAND_SEND_UNRELIABLE_FRAGMENT, 12). 17 | 18 | 19 | 20 | %% 21 | %% Protocol Header 22 | %% 23 | 24 | -define(PROTOCOL_HEADER(SentTimeIncluded, Compressed, SessionID, PeerID, 25 | Rest), 26 | << 27 | (SentTimeIncluded) :1, 28 | (Compressed) :1, 29 | (SessionID) :2, 30 | (PeerID) :12, 31 | (Rest) /binary 32 | >> 33 | ). 34 | 35 | 36 | %% 37 | %% Command Header 38 | %% 39 | 40 | -define(COMMAND_HEADER(Ack, IsUnsequenced, CommandType, ChannelID, 41 | ReliableSequenceNumber, Command), 42 | << 43 | (Ack) :1, 44 | (IsUnsequenced) :1, 45 | (CommandType) :6, 46 | (ChannelID) :8, 47 | (ReliableSequenceNumber) :16, 48 | (Command) /binary 49 | >> 50 | ). 51 | 52 | 53 | %% 54 | %% Acknowledge Command 55 | %% 56 | 57 | -define(ACKNOWLEDGE(ReceivedReliableSequenceNumber, ReceivedSentTime, Rest), 58 | << 59 | (ReceivedReliableSequenceNumber) :16, 60 | (ReceivedSentTime) :16, 61 | (Rest) /binary 62 | >> 63 | ). 64 | 65 | 66 | %% 67 | %% Connect Command 68 | %% 69 | 70 | -define(CONNECT(OutgoingPeerID, IncomingSessionID, OutgoingSessionID, MTU, 71 | WindowSize, ChannelCount, IncomingBandwidth, OutgoingBandwidth, 72 | PacketThrottleInterval, PacketThrottleAcceleration, 73 | PacketThrottleDeceleration, ConnectID, Data, Rest), 74 | << 75 | (OutgoingPeerID) :16, 76 | (IncomingSessionID) :8, 77 | (OutgoingSessionID) :8, 78 | (MTU) :32, 79 | (WindowSize) :32, 80 | (ChannelCount) :32, 81 | (IncomingBandwidth) :32, 82 | (OutgoingBandwidth) :32, 83 | (PacketThrottleInterval) :32, 84 | (PacketThrottleAcceleration) :32, 85 | (PacketThrottleDeceleration) :32, 86 | (ConnectID) :32, 87 | (Data) :32, 88 | (Rest) /binary 89 | >> 90 | ). 91 | 92 | 93 | %% 94 | %% Verify Connect Command 95 | %% 96 | 97 | -define(VERIFY_CONNECT(OutgoingPeerID, IncomingSessionID, OutgoingSessionID, 98 | MTU, WindowSize, ChannelCount, IncomingBandwidth, 99 | OutgoingBandwidth, PacketThrottleInterval, 100 | PacketThrottleAcceleration, PacketThrottleDeceleration, 101 | ConnectID, Rest), 102 | << 103 | (OutgoingPeerID) :16, 104 | (IncomingSessionID) :8, 105 | (OutgoingSessionID) :8, 106 | (MTU) :32, 107 | (WindowSize) :32, 108 | (ChannelCount) :32, 109 | (IncomingBandwidth) :32, 110 | (OutgoingBandwidth) :32, 111 | (PacketThrottleInterval) :32, 112 | (PacketThrottleAcceleration) :32, 113 | (PacketThrottleDeceleration) :32, 114 | (ConnectID) :32, 115 | (Rest) /binary 116 | >> 117 | ). 118 | 119 | 120 | %% 121 | %% Disconnect Command 122 | %% 123 | 124 | -define(DISCONNECT(Data, Rest), 125 | << 126 | (Data) :32, 127 | (Rest) /binary 128 | >> 129 | ). 130 | 131 | 132 | %% 133 | %% Ping Command 134 | %% 135 | 136 | -define(PING(Rest), 137 | << 138 | (Rest) /binary 139 | >> 140 | ). 141 | 142 | 143 | %% 144 | %% Send Reliable Command 145 | %% 146 | 147 | -define(SEND_RELIABLE(DataLength, Data), 148 | << 149 | (DataLength) :16, 150 | (Data) /binary 151 | >> 152 | ). 153 | 154 | 155 | %% 156 | %% Send Unreliable Command 157 | %% 158 | 159 | -define(SEND_UNRELIABLE(UnreliableSequenceNumber, DataLength, Data), 160 | << 161 | (UnreliableSequenceNumber) :16, 162 | (DataLength) :16, 163 | (Data) /binary 164 | >> 165 | ). 166 | 167 | 168 | %% 169 | %% Send Unsequenced Command 170 | %% 171 | 172 | -define(SEND_UNSEQUENCED(UnsequencedGroup, DataLength, Data), 173 | << 174 | (UnsequencedGroup) :16, 175 | (DataLength) :16, 176 | (Data) /binary 177 | >> 178 | ). 179 | 180 | 181 | %% 182 | %% Send Fragment Command 183 | %% 184 | 185 | -define(SEND_FRAGMENT(StartSequenceNumber, DataLength, FragmentCount, 186 | FragmentNumber, TotalLength, FragmentOffset, Data), 187 | << 188 | (StartSequenceNumber) :16, 189 | (DataLength) :16, 190 | (FragmentCount) :32, 191 | (FragmentNumber) :32, 192 | (TotalLength) :32, 193 | (FragmentOffset) :32, 194 | (Data) /binary 195 | >> 196 | ). 197 | 198 | 199 | %% 200 | %% Bandwidth Limit Command 201 | %% 202 | 203 | -define(BANDWIDTH_LIMIT(IncomingBandwidth, OutgoingBandwidth, Rest), 204 | << 205 | (IncomingBandwidth) :32, 206 | (OutgoingBandwidth) :32, 207 | (Rest) /binary 208 | >> 209 | ). 210 | 211 | 212 | %% 213 | %% Throttle Configure Command 214 | %% 215 | 216 | -define(THROTTLE_CONFIGURE(PacketThrottleInterval, PacketThrottleAcceleration, 217 | PacketThrottleDeceleration, Rest), 218 | << 219 | (PacketThrottleInterval) :32, 220 | (PacketThrottleAcceleration) :32, 221 | (PacketThrottleDeceleration) :32, 222 | (Rest) /binary 223 | >> 224 | ). 225 | -------------------------------------------------------------------------------- /src/enet_protocol_decode.erl: -------------------------------------------------------------------------------- 1 | -module(enet_protocol_decode). 2 | 3 | -include("enet_protocol.hrl"). 4 | -include("enet_commands.hrl"). 5 | 6 | -export([ 7 | protocol_header/1, 8 | commands/1 9 | ]). 10 | 11 | 12 | 13 | %%% 14 | %%% Protocol Header 15 | %%% 16 | 17 | protocol_header(?PROTOCOL_HEADER(0, Compressed, SessionID, PeerID, Commands)) -> 18 | Header = #protocol_header{ 19 | compressed = Compressed, 20 | session_id = SessionID, 21 | peer_id = PeerID 22 | }, 23 | {ok, Header, Commands}; 24 | 25 | protocol_header(?PROTOCOL_HEADER(1, Compressed, SessionID, PeerID, Rest)) -> 26 | <> = Rest, 27 | Header = #protocol_header{ 28 | compressed = Compressed, 29 | session_id = SessionID, 30 | peer_id = PeerID, 31 | sent_time = SentTime 32 | }, 33 | {ok, Header, Commands}. 34 | 35 | 36 | commands(Bin) -> 37 | {ok, commands(Bin, [])}. 38 | 39 | commands(<<>>, Acc) -> 40 | lists:reverse(Acc); 41 | commands(Bin, Acc) -> 42 | {ok, Header, Rest} = command_header(Bin), 43 | {ok, Command, CommandsBin} = command(Header#command_header.command, Rest), 44 | commands(CommandsBin, [{Header, Command} | Acc]). 45 | 46 | 47 | %%% 48 | %%% Command Header 49 | %%% 50 | 51 | command_header(?COMMAND_HEADER( 52 | Ack, IsUnsequenced, CommandType, ChannelID, 53 | ReliableSequenceNumber, Rest)) -> 54 | Header = #command_header{ 55 | please_acknowledge = Ack, 56 | unsequenced = IsUnsequenced, 57 | command = CommandType, 58 | channel_id = ChannelID, 59 | reliable_sequence_number = ReliableSequenceNumber 60 | }, 61 | {ok, Header, Rest}. 62 | 63 | 64 | %%% 65 | %%% Commands 66 | %%% 67 | 68 | command(?COMMAND_ACKNOWLEDGE, 69 | ?ACKNOWLEDGE( 70 | ReceivedReliableSequenceNumber, ReceivedSentTime, Rest)) -> 71 | Command = 72 | #acknowledge{ 73 | received_reliable_sequence_number = ReceivedReliableSequenceNumber, 74 | received_sent_time = ReceivedSentTime 75 | }, 76 | {ok, Command, Rest}; 77 | 78 | command(?COMMAND_CONNECT, 79 | ?CONNECT( 80 | OutgoingPeerID, IncomingSessionID, OutgoingSessionID, MTU, 81 | WindowSize, ChannelCount, IncomingBandwidth, OutgoingBandwidth, 82 | PacketThrottleInterval, PacketThrottleAcceleration, 83 | PacketThrottleDeceleration, ConnectID, Data, Rest)) -> 84 | Command = 85 | #connect{ 86 | outgoing_peer_id = OutgoingPeerID, 87 | incoming_session_id = IncomingSessionID, 88 | outgoing_session_id = OutgoingSessionID, 89 | mtu = MTU, 90 | window_size = WindowSize, 91 | channel_count = ChannelCount, 92 | incoming_bandwidth = IncomingBandwidth, 93 | outgoing_bandwidth = OutgoingBandwidth, 94 | packet_throttle_interval = PacketThrottleInterval, 95 | packet_throttle_acceleration = PacketThrottleAcceleration, 96 | packet_throttle_deceleration = PacketThrottleDeceleration, 97 | connect_id = ConnectID, 98 | data = Data 99 | }, 100 | {ok, Command, Rest}; 101 | 102 | command(?COMMAND_VERIFY_CONNECT, 103 | ?VERIFY_CONNECT( 104 | OutgoingPeerID, IncomingSessionID, OutgoingSessionID, MTU, 105 | WindowSize, ChannelCount, IncomingBandwidth, OutgoingBandwidth, 106 | PacketThrottleInterval, PacketThrottleAcceleration, 107 | PacketThrottleDeceleration, ConnectID, Rest)) -> 108 | Command = 109 | #verify_connect{ 110 | outgoing_peer_id = OutgoingPeerID, 111 | incoming_session_id = IncomingSessionID, 112 | outgoing_session_id = OutgoingSessionID, 113 | mtu = MTU, 114 | window_size = WindowSize, 115 | channel_count = ChannelCount, 116 | incoming_bandwidth = IncomingBandwidth, 117 | outgoing_bandwidth = OutgoingBandwidth, 118 | packet_throttle_interval = PacketThrottleInterval, 119 | packet_throttle_acceleration = PacketThrottleAcceleration, 120 | packet_throttle_deceleration = PacketThrottleDeceleration, 121 | connect_id = ConnectID 122 | }, 123 | {ok, Command, Rest}; 124 | 125 | command(?COMMAND_DISCONNECT, 126 | ?DISCONNECT(Data, Rest)) -> 127 | Command = 128 | #disconnect{ 129 | data = Data 130 | }, 131 | {ok, Command, Rest}; 132 | 133 | command(?COMMAND_PING, 134 | ?PING(Rest)) -> 135 | Command = #ping{}, 136 | {ok, Command, Rest}; 137 | 138 | command(?COMMAND_SEND_RELIABLE, 139 | ?SEND_RELIABLE(DataLength, DataRest)) -> 140 | <> = DataRest, 141 | Command = 142 | #reliable{ 143 | data = Data 144 | }, 145 | {ok, Command, Rest}; 146 | 147 | command(?COMMAND_SEND_UNRELIABLE, 148 | ?SEND_UNRELIABLE(SequenceNumber, DataLength, DataRest)) -> 149 | <> = DataRest, 150 | Command = #unreliable{ 151 | sequence_number = SequenceNumber, 152 | data = Data 153 | }, 154 | {ok, Command, Rest}; 155 | 156 | command(?COMMAND_SEND_UNSEQUENCED, 157 | ?SEND_UNSEQUENCED(UnsequencedGroup, DataLength, DataRest)) -> 158 | <> = DataRest, 159 | Command = 160 | #unsequenced{ 161 | group = UnsequencedGroup, 162 | data = Data 163 | }, 164 | {ok, Command, Rest}; 165 | 166 | command(?COMMAND_SEND_FRAGMENT, 167 | ?SEND_FRAGMENT( 168 | StartSequenceNumber, DataLength, FragmentCount, FragmentNumber, 169 | TotalLength, FragmentOffset, DataRest)) -> 170 | <> = DataRest, 171 | Command = 172 | #fragment{ 173 | start_sequence_number = StartSequenceNumber, 174 | fragment_count = FragmentCount, 175 | fragment_number = FragmentNumber, 176 | total_length = TotalLength, 177 | fragment_offset = FragmentOffset, 178 | data = Data 179 | }, 180 | {ok, Command, Rest}; 181 | 182 | command(?COMMAND_BANDWIDTH_LIMIT, 183 | ?BANDWIDTH_LIMIT(IncomingBandwidth, OutgoingBandwidth, Rest)) -> 184 | Command = 185 | #bandwidth_limit{ 186 | incoming_bandwidth = IncomingBandwidth, 187 | outgoing_bandwidth = OutgoingBandwidth 188 | }, 189 | {ok, Command, Rest}; 190 | 191 | command(?COMMAND_THROTTLE_CONFIGURE, 192 | ?THROTTLE_CONFIGURE( 193 | PacketThrottleInterval, PacketThrottleAcceleration, 194 | PacketThrottleDeceleration, Rest)) -> 195 | Command = 196 | #throttle_configure{ 197 | packet_throttle_interval = PacketThrottleInterval, 198 | packet_throttle_acceleration = PacketThrottleAcceleration, 199 | packet_throttle_deceleration = PacketThrottleDeceleration 200 | }, 201 | {ok, Command, Rest}. 202 | -------------------------------------------------------------------------------- /src/enet_protocol_encode.erl: -------------------------------------------------------------------------------- 1 | -module(enet_protocol_encode). 2 | 3 | -include("enet_protocol.hrl"). 4 | -include("enet_commands.hrl"). 5 | 6 | -export([ protocol_header/1 7 | , command_header/1 8 | , command/1 9 | ]). 10 | 11 | 12 | %%% 13 | %%% Protocol Header 14 | %%% 15 | 16 | protocol_header(PH = #protocol_header{ sent_time = undefined }) -> 17 | ?PROTOCOL_HEADER(0, 18 | PH#protocol_header.compressed, 19 | PH#protocol_header.session_id, 20 | PH#protocol_header.peer_id, 21 | <<>>); 22 | 23 | protocol_header(PH = #protocol_header{ sent_time = SentTime }) -> 24 | ?PROTOCOL_HEADER(1, 25 | PH#protocol_header.compressed, 26 | PH#protocol_header.session_id, 27 | PH#protocol_header.peer_id, 28 | <>). 29 | 30 | 31 | %%% 32 | %%% Command Header 33 | %%% 34 | 35 | command_header(CH = #command_header{}) -> 36 | ?COMMAND_HEADER(CH#command_header.please_acknowledge, 37 | CH#command_header.unsequenced, 38 | CH#command_header.command, 39 | CH#command_header.channel_id, 40 | CH#command_header.reliable_sequence_number, 41 | <<>>). 42 | 43 | 44 | %%% 45 | %%% Commands 46 | %%% 47 | 48 | command(C = #acknowledge{}) -> 49 | ?ACKNOWLEDGE(C#acknowledge.received_reliable_sequence_number, 50 | C#acknowledge.received_sent_time, 51 | <<>>); 52 | 53 | command(C = #connect{}) -> 54 | ?CONNECT(C#connect.outgoing_peer_id, 55 | C#connect.incoming_session_id, 56 | C#connect.outgoing_session_id, 57 | C#connect.mtu, 58 | C#connect.window_size, 59 | C#connect.channel_count, 60 | C#connect.incoming_bandwidth, 61 | C#connect.outgoing_bandwidth, 62 | C#connect.packet_throttle_interval, 63 | C#connect.packet_throttle_acceleration, 64 | C#connect.packet_throttle_deceleration, 65 | C#connect.connect_id, 66 | C#connect.data, 67 | <<>>); 68 | 69 | command(C = #verify_connect{}) -> 70 | ?VERIFY_CONNECT(C#verify_connect.outgoing_peer_id, 71 | C#verify_connect.incoming_session_id, 72 | C#verify_connect.outgoing_session_id, 73 | C#verify_connect.mtu, 74 | C#verify_connect.window_size, 75 | C#verify_connect.channel_count, 76 | C#verify_connect.incoming_bandwidth, 77 | C#verify_connect.outgoing_bandwidth, 78 | C#verify_connect.packet_throttle_interval, 79 | C#verify_connect.packet_throttle_acceleration, 80 | C#verify_connect.packet_throttle_deceleration, 81 | C#verify_connect.connect_id, 82 | <<>>); 83 | 84 | command(C = #disconnect{}) -> 85 | ?DISCONNECT(C#disconnect.data, 86 | <<>>); 87 | 88 | command(#ping{}) -> 89 | ?PING(<<>>); 90 | 91 | command(C = #reliable{}) -> 92 | ?SEND_RELIABLE(byte_size(C#reliable.data), 93 | C#reliable.data); 94 | 95 | command(C = #unreliable{}) -> 96 | ?SEND_UNRELIABLE(C#unreliable.sequence_number, 97 | byte_size(C#unreliable.data), 98 | C#unreliable.data); 99 | 100 | command(C = #unsequenced{}) -> 101 | ?SEND_UNSEQUENCED(C#unsequenced.group, 102 | byte_size(C#unsequenced.data), 103 | C#unsequenced.data); 104 | 105 | command(C = #fragment{}) -> 106 | ?SEND_FRAGMENT(C#fragment.start_sequence_number, 107 | byte_size(C#fragment.data), 108 | C#fragment.fragment_count, 109 | C#fragment.fragment_number, 110 | C#fragment.total_length, 111 | C#fragment.fragment_offset, 112 | C#fragment.data); 113 | 114 | command(C = #bandwidth_limit{}) -> 115 | ?BANDWIDTH_LIMIT(C#bandwidth_limit.incoming_bandwidth, 116 | C#bandwidth_limit.outgoing_bandwidth, 117 | <<>>); 118 | 119 | command(C = #throttle_configure{}) -> 120 | ?THROTTLE_CONFIGURE(C#throttle_configure.packet_throttle_interval, 121 | C#throttle_configure.packet_throttle_acceleration, 122 | C#throttle_configure.packet_throttle_deceleration, 123 | <<>>). 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/enet_sup.erl: -------------------------------------------------------------------------------- 1 | -module(enet_sup). 2 | -behaviour(supervisor). 3 | 4 | %% API 5 | -export([ 6 | start_link/0, 7 | start_host_supervisor/3, 8 | stop_host_supervisor/1 9 | ]). 10 | 11 | %% Supervisor callbacks 12 | -export([ init/1 ]). 13 | 14 | -define(SERVER, ?MODULE). 15 | 16 | 17 | %%%=================================================================== 18 | %%% API functions 19 | %%%=================================================================== 20 | 21 | start_link() -> 22 | supervisor:start_link({local, ?SERVER}, ?MODULE, []). 23 | 24 | start_host_supervisor(Port, ConnectFun, Options) -> 25 | Child = #{ 26 | id => Port, 27 | start => {enet_host_sup, start_link, [Port, ConnectFun, Options]}, 28 | restart => temporary, 29 | shutdown => infinity, 30 | type => supervisor, 31 | modules => [enet_host_sup] 32 | }, 33 | supervisor:start_child(?MODULE, Child). 34 | 35 | stop_host_supervisor(HostSup) -> 36 | supervisor:terminate_child(?MODULE, HostSup). 37 | 38 | 39 | %%%=================================================================== 40 | %%% Supervisor callbacks 41 | %%%=================================================================== 42 | 43 | init([]) -> 44 | SupFlags = #{ 45 | strategy => one_for_one, 46 | intensity => 1, 47 | period => 5 48 | }, 49 | {ok, {SupFlags, []}}. 50 | 51 | 52 | %%%=================================================================== 53 | %%% Internal functions 54 | %%%=================================================================== 55 | -------------------------------------------------------------------------------- /test/enet_api_SUITE.erl: -------------------------------------------------------------------------------- 1 | -module(enet_api_SUITE). 2 | -compile(export_all). 3 | 4 | -include_lib("common_test/include/ct.hrl"). 5 | -include_lib("enet/include/enet.hrl"). 6 | 7 | 8 | suite() -> 9 | [{timetrap,{seconds,30}}]. 10 | 11 | init_per_suite(Config) -> 12 | application:ensure_all_started(enet), 13 | Config. 14 | 15 | end_per_suite(_Config) -> 16 | ok. 17 | 18 | init_per_group(_GroupName, Config) -> 19 | Config. 20 | 21 | end_per_group(_GroupName, _Config) -> 22 | ok. 23 | 24 | init_per_testcase(_TestCase, Config) -> 25 | Config. 26 | 27 | end_per_testcase(_TestCase, _Config) -> 28 | ok. 29 | 30 | groups() -> 31 | []. 32 | 33 | all() -> 34 | [ 35 | local_worker_init_error_test, 36 | local_zero_peer_limit_test, 37 | remote_zero_peer_limit_test, 38 | broadcast_connect_test, 39 | local_disconnect_test, 40 | remote_disconnect_test, 41 | unsequenced_messages_test, 42 | unreliable_messages_test, 43 | reliable_messages_test, 44 | unsequenced_broadcast_test, 45 | unreliable_broadcast_test, 46 | reliable_broadcast_test 47 | ]. 48 | 49 | 50 | local_worker_init_error_test(_Config) -> 51 | ConnectFun = fun(_PeerInfo) -> {error, whatever} end, 52 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 1}]), 53 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 1}]), 54 | {ok, LocalPeer} = enet:connect_peer(LocalHost, "127.0.0.1", RemoteHost, 1), 55 | Ref = monitor(process, LocalPeer), 56 | receive 57 | {'DOWN', Ref, process, LocalPeer, {worker_init_error, whatever}} -> ok 58 | after 1000 -> 59 | exit(local_peer_did_not_stop_on_worker_init_error) 60 | end, 61 | ok = enet:stop_host(LocalHost), 62 | ok = enet:stop_host(RemoteHost). 63 | 64 | local_zero_peer_limit_test(_Config) -> 65 | Self = self(), 66 | ConnectFun = fun(_PeerInfo) -> {ok, Self} end, 67 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 0}]), 68 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 1}]), 69 | {error, reached_peer_limit} = 70 | enet:connect_peer(LocalHost, "127.0.0.1", RemoteHost, 1), 71 | ok = enet:stop_host(LocalHost), 72 | ok = enet:stop_host(RemoteHost). 73 | 74 | remote_zero_peer_limit_test(_Config) -> 75 | Self = self(), 76 | ConnectFun = fun(PeerInfo) -> 77 | Self ! PeerInfo, 78 | {ok, Self} 79 | end, 80 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 1}]), 81 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 0}]), 82 | {ok, Peer} = enet:connect_peer(LocalHost, "127.0.0.1", RemoteHost, 1), 83 | receive 84 | #{peer := Peer} -> 85 | exit(peer_could_connect_despite_peer_limit_reached) 86 | after 200 -> %% How long time is enough? (This is ugly) 87 | ok 88 | end, 89 | ok = enet:stop_host(LocalHost), 90 | ok = enet:stop_host(RemoteHost). 91 | 92 | broadcast_connect_test(_Config) -> 93 | Self = self(), 94 | ConnectFun = fun(PeerInfo) -> 95 | Self ! PeerInfo, 96 | {ok, Self} 97 | end, 98 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 1}]), 99 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 1}]), 100 | {ok, LocalPeer} = 101 | enet:connect_peer(LocalHost, "255.255.255.255", RemoteHost, 1), 102 | ConnectID = 103 | receive 104 | #{peer := LocalPeer, connect_id := C} -> C 105 | after 1000 -> 106 | exit(local_peer_did_not_notify_worker) 107 | end, 108 | RemotePeer = 109 | receive 110 | #{peer := P, connect_id := ConnectID} -> P 111 | after 1000 -> 112 | exit(remote_peer_did_not_notify_worker) 113 | end, 114 | Ref1 = monitor(process, LocalPeer), 115 | Ref2 = monitor(process, RemotePeer), 116 | ok = enet:disconnect_peer(LocalPeer), 117 | receive 118 | {enet, disconnected, local, LocalPeer, ConnectID} -> ok 119 | after 1000 -> 120 | exit(local_peer_did_not_notify_worker) 121 | end, 122 | receive 123 | {enet, disconnected, remote, RemotePeer, ConnectID} -> ok 124 | after 1000 -> 125 | exit(remote_peer_did_not_notify_worker) 126 | end, 127 | receive 128 | {'DOWN', Ref1, process, LocalPeer, normal} -> ok 129 | after 1000 -> 130 | exit(local_peer_did_not_exit) 131 | end, 132 | receive 133 | {'DOWN', Ref2, process, RemotePeer, normal} -> ok 134 | after 1000 -> 135 | exit(remote_peer_did_not_exit) 136 | end, 137 | ok = enet:stop_host(LocalHost), 138 | ok = enet:stop_host(RemoteHost). 139 | 140 | local_disconnect_test(_Config) -> 141 | Self = self(), 142 | ConnectFun = fun(PeerInfo) -> 143 | Self ! PeerInfo, 144 | {ok, Self} 145 | end, 146 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 147 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 148 | {ok, LocalPeer} = enet:connect_peer(LocalHost, "127.0.0.1", RemoteHost, 1), 149 | ConnectID = 150 | receive 151 | #{peer := LocalPeer, connect_id := C} -> C 152 | after 1000 -> 153 | exit(local_peer_did_not_notify_worker) 154 | end, 155 | RemotePeer = 156 | receive 157 | #{peer := P, connect_id := ConnectID} -> P 158 | after 1000 -> 159 | exit(remote_peer_did_not_notify_worker) 160 | end, 161 | Ref1 = monitor(process, LocalPeer), 162 | Ref2 = monitor(process, RemotePeer), 163 | ok = enet:disconnect_peer(LocalPeer), 164 | receive 165 | {enet, disconnected, local, LocalPeer, ConnectID} -> ok 166 | after 1000 -> 167 | exit(local_peer_did_not_notify_worker) 168 | end, 169 | receive 170 | {enet, disconnected, remote, RemotePeer, ConnectID} -> ok 171 | after 1000 -> 172 | exit(remote_peer_did_not_notify_worker) 173 | end, 174 | receive 175 | {'DOWN', Ref1, process, LocalPeer, normal} -> ok 176 | after 1000 -> 177 | exit(local_peer_did_not_exit) 178 | end, 179 | receive 180 | {'DOWN', Ref2, process, RemotePeer, normal} -> ok 181 | after 1000 -> 182 | exit(remote_peer_did_not_exit) 183 | end, 184 | ok = enet:stop_host(LocalHost), 185 | ok = enet:stop_host(RemoteHost). 186 | 187 | remote_disconnect_test(_Config) -> 188 | Self = self(), 189 | ConnectFun = fun(PeerInfo) -> 190 | Self ! PeerInfo, 191 | {ok, Self} 192 | end, 193 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 194 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 195 | {ok, LocalPeer} = enet:connect_peer(LocalHost, "127.0.0.1", RemoteHost, 1), 196 | ConnectID = 197 | receive 198 | #{peer := LocalPeer, connect_id := C} -> C 199 | after 1000 -> 200 | exit(local_peer_did_not_notify_worker) 201 | end, 202 | RemotePeer = 203 | receive 204 | #{peer := P, connect_id := ConnectID} -> P 205 | after 1000 -> 206 | exit(remote_peer_did_not_notify_worker) 207 | end, 208 | Ref1 = monitor(process, LocalPeer), 209 | Ref2 = monitor(process, RemotePeer), 210 | ok = enet:disconnect_peer(RemotePeer), 211 | receive 212 | {enet, disconnected, local, _LocalPeer, ConnectID} -> ok 213 | after 1000 -> 214 | exit(local_peer_did_not_notify_worker) 215 | end, 216 | receive 217 | {enet, disconnected, remote, _RemotePeer, ConnectID} -> ok 218 | after 1000 -> 219 | exit(remote_peer_did_not_notify_worker) 220 | end, 221 | receive 222 | {'DOWN', Ref1, process, LocalPeer, normal} -> ok 223 | after 1000 -> 224 | exit(local_peer_did_not_exit) 225 | end, 226 | receive 227 | {'DOWN', Ref2, process, RemotePeer, normal} -> ok 228 | after 1000 -> 229 | exit(remote_peer_did_not_exit) 230 | end, 231 | ok = enet:stop_host(LocalHost), 232 | ok = enet:stop_host(RemoteHost). 233 | 234 | unsequenced_messages_test(_Config) -> 235 | Self = self(), 236 | ConnectFun = fun(PeerInfo) -> 237 | Self ! PeerInfo, 238 | {ok, Self} 239 | end, 240 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 241 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 242 | {ok, LocalPeer} = enet:connect_peer(LocalHost, "127.0.0.1", RemoteHost, 1), 243 | {ConnectID, LocalChannels} = 244 | receive 245 | #{peer := LocalPeer, channels := LCs, connect_id := C} -> {C, LCs} 246 | after 1000 -> 247 | exit(local_peer_did_not_notify_worker) 248 | end, 249 | RemoteChannels = 250 | receive 251 | #{channels := RCs, connect_id := ConnectID} -> RCs 252 | after 1000 -> 253 | exit(remote_peer_did_not_notify_worker) 254 | end, 255 | {ok, LocalChannel1} = maps:find(0, LocalChannels), 256 | {ok, RemoteChannel1} = maps:find(0, RemoteChannels), 257 | ok = enet:send_unsequenced(LocalChannel1, <<"local->remote">>), 258 | ok = enet:send_unsequenced(RemoteChannel1, <<"remote->local">>), 259 | receive 260 | {enet, 0, #unsequenced{ data = <<"local->remote">> }} -> ok 261 | after 500 -> 262 | exit(remote_channel_did_not_send_data_to_worker) 263 | end, 264 | receive 265 | {enet, 0, #unsequenced{ data = <<"remote->local">> }} -> ok 266 | after 500 -> 267 | exit(local_channel_did_not_send_data_to_worker) 268 | end, 269 | ok = enet:stop_host(LocalHost), 270 | ok = enet:stop_host(RemoteHost). 271 | 272 | unreliable_messages_test(_Config) -> 273 | Self = self(), 274 | ConnectFun = fun(PeerInfo) -> 275 | Self ! PeerInfo, 276 | {ok, Self} 277 | end, 278 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 279 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 280 | {ok, LocalPeer} = enet:connect_peer(LocalHost, "127.0.0.1", RemoteHost, 1), 281 | {ConnectID, LocalChannels} = 282 | receive 283 | #{peer := LocalPeer, channels := LCs, connect_id := C} -> {C, LCs} 284 | after 1000 -> 285 | exit(local_peer_did_not_notify_worker) 286 | end, 287 | RemoteChannels = 288 | receive 289 | #{channels := RCs, connect_id := ConnectID} -> RCs 290 | after 1000 -> 291 | exit(remote_peer_did_not_notify_worker) 292 | end, 293 | {ok, LocalChannel1} = maps:find(0, LocalChannels), 294 | {ok, RemoteChannel1} = maps:find(0, RemoteChannels), 295 | ok = enet:send_unreliable(LocalChannel1, <<"local->remote 1">>), 296 | ok = enet:send_unreliable(RemoteChannel1, <<"remote->local 1">>), 297 | ok = enet:send_unreliable(LocalChannel1, <<"local->remote 2">>), 298 | ok = enet:send_unreliable(RemoteChannel1, <<"remote->local 2">>), 299 | receive 300 | {enet, 0, #unreliable{ data = <<"local->remote 1">> }} -> ok 301 | after 500 -> 302 | exit(remote_channel_did_not_send_data_to_worker) 303 | end, 304 | receive 305 | {enet, 0, #unreliable{ data = <<"remote->local 1">> }} -> ok 306 | after 500 -> 307 | exit(local_channel_did_not_send_data_to_worker) 308 | end, 309 | receive 310 | {enet, 0, #unreliable{ data = <<"local->remote 2">> }} -> ok 311 | after 500 -> 312 | exit(remote_channel_did_not_send_data_to_worker) 313 | end, 314 | receive 315 | {enet, 0, #unreliable{ data = <<"remote->local 2">> }} -> ok 316 | after 500 -> 317 | exit(local_channel_did_not_send_data_to_worker) 318 | end, 319 | ok = enet:stop_host(LocalHost), 320 | ok = enet:stop_host(RemoteHost). 321 | 322 | reliable_messages_test(_Config) -> 323 | Self = self(), 324 | ConnectFun = fun(PeerInfo) -> 325 | Self ! PeerInfo, 326 | {ok, Self} 327 | end, 328 | {ok, LocalHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 329 | {ok, RemoteHost} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 330 | {ok, LocalPeer} = enet:connect_peer(LocalHost, "127.0.0.1", RemoteHost, 1), 331 | {ConnectID, LocalChannels} = 332 | receive 333 | #{peer := LocalPeer, channels := LCs, connect_id := C} -> {C, LCs} 334 | after 1000 -> 335 | exit(local_peer_did_not_notify_worker) 336 | end, 337 | RemoteChannels = 338 | receive 339 | #{channels := RCs, connect_id := ConnectID} -> RCs 340 | after 1000 -> 341 | exit(remote_peer_did_not_notify_worker) 342 | end, 343 | {ok, LocalChannel1} = maps:find(0, LocalChannels), 344 | {ok, RemoteChannel1} = maps:find(0, RemoteChannels), 345 | ok = enet:send_reliable(LocalChannel1, <<"local->remote 1">>), 346 | ok = enet:send_reliable(RemoteChannel1, <<"remote->local 1">>), 347 | ok = enet:send_reliable(LocalChannel1, <<"local->remote 2">>), 348 | ok = enet:send_reliable(RemoteChannel1, <<"remote->local 2">>), 349 | receive 350 | {enet, 0, #reliable{ data = <<"local->remote 1">> }} -> ok 351 | after 500 -> 352 | exit(remote_channel_did_not_send_data_to_worker) 353 | end, 354 | receive 355 | {enet, 0, #reliable{ data = <<"remote->local 1">> }} -> ok 356 | after 500 -> 357 | exit(local_channel_did_not_send_data_to_worker) 358 | end, 359 | receive 360 | {enet, 0, #reliable{ data = <<"local->remote 2">> }} -> ok 361 | after 500 -> 362 | exit(remote_channel_did_not_send_data_to_worker) 363 | end, 364 | receive 365 | {enet, 0, #reliable{ data = <<"remote->local 2">> }} -> ok 366 | after 500 -> 367 | exit(local_channel_did_not_send_data_to_worker) 368 | end, 369 | ok = enet:stop_host(LocalHost), 370 | ok = enet:stop_host(RemoteHost). 371 | 372 | unsequenced_broadcast_test(_Config) -> 373 | Self = self(), 374 | ConnectFun = fun(PeerInfo) -> 375 | Self ! PeerInfo, 376 | {ok, Self} 377 | end, 378 | {ok, Host1} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 379 | {ok, Host2} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 380 | {ok, Host3} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 381 | {ok, Peer12} = enet:connect_peer(Host1, "127.0.0.1", Host2, 1), 382 | ConnectID12 = 383 | receive 384 | #{peer := Peer12, connect_id := CID12} -> CID12 385 | after 1000 -> 386 | exit(peer12_did_not_notify_worker) 387 | end, 388 | receive 389 | #{connect_id := ConnectID12} -> ok 390 | after 1000 -> 391 | exit(peer21_did_not_notify_worker) 392 | end, 393 | {ok, Peer23} = enet:connect_peer(Host2, "127.0.0.1", Host3, 1), 394 | ConnectID23 = 395 | receive 396 | #{peer := Peer23, connect_id := CID23} -> CID23 397 | after 1000 -> 398 | exit(peer23_did_not_notify_worker) 399 | end, 400 | receive 401 | #{connect_id := ConnectID23} -> ok 402 | after 1000 -> 403 | exit(peer32_did_not_notify_worker) 404 | end, 405 | {ok, Peer31} = enet:connect_peer(Host3, "127.0.0.1", Host1, 1), 406 | ConnectID31 = 407 | receive 408 | #{peer := Peer31, connect_id := CID31} -> CID31 409 | after 1000 -> 410 | exit(peer31_did_not_notify_worker) 411 | end, 412 | receive 413 | #{connect_id := ConnectID31} -> ok 414 | after 1000 -> 415 | exit(peer13_did_not_notify_worker) 416 | end, 417 | ok = enet:broadcast_unsequenced(Host1, 0, <<"host1->broadcast">>), 418 | ok = enet:broadcast_unsequenced(Host2, 0, <<"host2->broadcast">>), 419 | ok = enet:broadcast_unsequenced(Host3, 0, <<"host3->broadcast">>), 420 | receive 421 | {enet, 0, #unsequenced{ data = <<"host1->broadcast">> }} -> ok 422 | after 500 -> 423 | exit(channel_did_not_send_data_to_worker) 424 | end, 425 | receive 426 | {enet, 0, #unsequenced{ data = <<"host1->broadcast">> }} -> ok 427 | after 500 -> 428 | exit(channel_did_not_send_data_to_worker) 429 | end, 430 | receive 431 | {enet, 0, #unsequenced{ data = <<"host2->broadcast">> }} -> ok 432 | after 500 -> 433 | exit(channel_did_not_send_data_to_worker) 434 | end, 435 | receive 436 | {enet, 0, #unsequenced{ data = <<"host2->broadcast">> }} -> ok 437 | after 500 -> 438 | exit(channel_did_not_send_data_to_worker) 439 | end, 440 | receive 441 | {enet, 0, #unsequenced{ data = <<"host3->broadcast">> }} -> ok 442 | after 500 -> 443 | exit(channel_did_not_send_data_to_worker) 444 | end, 445 | receive 446 | {enet, 0, #unsequenced{ data = <<"host3->broadcast">> }} -> ok 447 | after 500 -> 448 | exit(channel_did_not_send_data_to_worker) 449 | end, 450 | ok = enet:stop_host(Host1), 451 | ok = enet:stop_host(Host2), 452 | ok = enet:stop_host(Host3). 453 | 454 | unreliable_broadcast_test(_Config) -> 455 | Self = self(), 456 | ConnectFun = fun(PeerInfo) -> 457 | Self ! PeerInfo, 458 | {ok, Self} 459 | end, 460 | {ok, Host1} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 461 | {ok, Host2} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 462 | {ok, Host3} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 463 | {ok, Peer12} = enet:connect_peer(Host1, "127.0.0.1", Host2, 1), 464 | ConnectID12 = 465 | receive 466 | #{peer := Peer12, connect_id := CID12} -> CID12 467 | after 1000 -> 468 | exit(peer12_did_not_notify_worker) 469 | end, 470 | receive 471 | #{connect_id := ConnectID12} -> ok 472 | after 1000 -> 473 | exit(peer21_did_not_notify_worker) 474 | end, 475 | {ok, Peer23} = enet:connect_peer(Host2, "127.0.0.1", Host3, 1), 476 | ConnectID23 = 477 | receive 478 | #{peer := Peer23, connect_id := CID23} -> CID23 479 | after 1000 -> 480 | exit(peer23_did_not_notify_worker) 481 | end, 482 | receive 483 | #{connect_id := ConnectID23} -> ok 484 | after 1000 -> 485 | exit(peer32_did_not_notify_worker) 486 | end, 487 | {ok, Peer31} = enet:connect_peer(Host3, "127.0.0.1", Host1, 1), 488 | ConnectID31 = 489 | receive 490 | #{peer := Peer31, connect_id := CID31} -> CID31 491 | after 1000 -> 492 | exit(peer31_did_not_notify_worker) 493 | end, 494 | receive 495 | #{connect_id := ConnectID31} -> ok 496 | after 1000 -> 497 | exit(peer13_did_not_notify_worker) 498 | end, 499 | ok = enet:broadcast_unreliable(Host1, 0, <<"host1->broadcast">>), 500 | ok = enet:broadcast_unreliable(Host2, 0, <<"host2->broadcast">>), 501 | ok = enet:broadcast_unreliable(Host3, 0, <<"host3->broadcast">>), 502 | receive 503 | {enet, 0, #unreliable{ data = <<"host1->broadcast">> }} -> ok 504 | after 500 -> 505 | exit(channel_did_not_send_data_to_worker) 506 | end, 507 | receive 508 | {enet, 0, #unreliable{ data = <<"host1->broadcast">> }} -> ok 509 | after 500 -> 510 | exit(channel_did_not_send_data_to_worker) 511 | end, 512 | receive 513 | {enet, 0, #unreliable{ data = <<"host2->broadcast">> }} -> ok 514 | after 500 -> 515 | exit(channel_did_not_send_data_to_worker) 516 | end, 517 | receive 518 | {enet, 0, #unreliable{ data = <<"host2->broadcast">> }} -> ok 519 | after 500 -> 520 | exit(channel_did_not_send_data_to_worker) 521 | end, 522 | receive 523 | {enet, 0, #unreliable{ data = <<"host3->broadcast">> }} -> ok 524 | after 500 -> 525 | exit(channel_did_not_send_data_to_worker) 526 | end, 527 | receive 528 | {enet, 0, #unreliable{ data = <<"host3->broadcast">> }} -> ok 529 | after 500 -> 530 | exit(channel_did_not_send_data_to_worker) 531 | end, 532 | ok = enet:stop_host(Host1), 533 | ok = enet:stop_host(Host2), 534 | ok = enet:stop_host(Host3). 535 | 536 | reliable_broadcast_test(_Config) -> 537 | Self = self(), 538 | ConnectFun = fun(PeerInfo) -> 539 | Self ! PeerInfo, 540 | {ok, Self} 541 | end, 542 | {ok, Host1} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 543 | {ok, Host2} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 544 | {ok, Host3} = enet:start_host(0, ConnectFun, [{peer_limit, 8}]), 545 | {ok, Peer12} = enet:connect_peer(Host1, "127.0.0.1", Host2, 1), 546 | ConnectID12 = 547 | receive 548 | #{peer := Peer12, connect_id := CID12} -> CID12 549 | after 1000 -> 550 | exit(peer12_did_not_notify_worker) 551 | end, 552 | receive 553 | #{connect_id := ConnectID12} -> ok 554 | after 1000 -> 555 | exit(peer21_did_not_notify_worker) 556 | end, 557 | {ok, Peer23} = enet:connect_peer(Host2, "127.0.0.1", Host3, 1), 558 | ConnectID23 = 559 | receive 560 | #{peer := Peer23, connect_id := CID23} -> CID23 561 | after 1000 -> 562 | exit(peer23_did_not_notify_worker) 563 | end, 564 | receive 565 | #{connect_id := ConnectID23} -> ok 566 | after 1000 -> 567 | exit(peer32_did_not_notify_worker) 568 | end, 569 | {ok, Peer31} = enet:connect_peer(Host3, "127.0.0.1", Host1, 1), 570 | ConnectID31 = 571 | receive 572 | #{peer := Peer31, connect_id := CID31} -> CID31 573 | after 1000 -> 574 | exit(peer31_did_not_notify_worker) 575 | end, 576 | receive 577 | #{connect_id := ConnectID31} -> ok 578 | 579 | after 1000 -> 580 | exit(peer13_did_not_notify_worker) 581 | end, 582 | ok = enet:broadcast_reliable(Host1, 0, <<"host1->broadcast">>), 583 | ok = enet:broadcast_reliable(Host2, 0, <<"host2->broadcast">>), 584 | ok = enet:broadcast_reliable(Host3, 0, <<"host3->broadcast">>), 585 | receive 586 | {enet, 0, #reliable{ data = <<"host1->broadcast">> }} -> ok 587 | after 500 -> 588 | exit(channel_did_not_send_data_to_worker) 589 | end, 590 | receive 591 | {enet, 0, #reliable{ data = <<"host1->broadcast">> }} -> ok 592 | after 500 -> 593 | exit(channel_did_not_send_data_to_worker) 594 | end, 595 | receive 596 | {enet, 0, #reliable{ data = <<"host2->broadcast">> }} -> ok 597 | after 500 -> 598 | exit(channel_did_not_send_data_to_worker) 599 | end, 600 | receive 601 | {enet, 0, #reliable{ data = <<"host2->broadcast">> }} -> ok 602 | after 500 -> 603 | exit(channel_did_not_send_data_to_worker) 604 | end, 605 | receive 606 | {enet, 0, #reliable{ data = <<"host3->broadcast">> }} -> ok 607 | after 500 -> 608 | exit(channel_did_not_send_data_to_worker) 609 | end, 610 | receive 611 | {enet, 0, #reliable{ data = <<"host3->broadcast">> }} -> ok 612 | after 500 -> 613 | exit(channel_did_not_send_data_to_worker) 614 | end, 615 | ok = enet:stop_host(Host1), 616 | ok = enet:stop_host(Host2), 617 | ok = enet:stop_host(Host3). 618 | -------------------------------------------------------------------------------- /test/enet_model.erl: -------------------------------------------------------------------------------- 1 | -module(enet_model). 2 | -behaviour(proper_statem). 3 | 4 | -include_lib("proper/include/proper.hrl"). 5 | 6 | -export([ 7 | initial_state/0, 8 | command/1, 9 | precondition/2, 10 | postcondition/3, 11 | next_state/3 12 | ]). 13 | 14 | -export([ 15 | mock_connect_fun/0, 16 | mock_start_worker/2, 17 | pretty_print_commands/1 18 | ]). 19 | 20 | 21 | -record(state, 22 | { 23 | hosts = [] 24 | }). 25 | 26 | -record(peer, 27 | { 28 | connect_id, 29 | pid, 30 | channel_count, 31 | channels = #{} 32 | }). 33 | 34 | 35 | %%% 36 | %%% Initial state 37 | %%% 38 | 39 | initial_state() -> 40 | #state{}. 41 | 42 | 43 | %%% 44 | %%% Commands 45 | %%% 46 | 47 | command(#state{ hosts = [] }) -> 48 | {call, enet_sync, start_host, [connect_fun(), host_options()]}; 49 | 50 | command(S) -> 51 | AlwaysPossibleCommands = 52 | [ 53 | {call, enet_sync, start_host, [connect_fun(), host_options()]}, 54 | 55 | ?LET(#{ port := Port }, started_host(S), 56 | {call, enet_sync, stop_host, [Port]}), 57 | 58 | connect_command(S) 59 | ], 60 | AllPeers = [P || H <- S#state.hosts, P <- maps:get(peers, H)], 61 | CommandsNeedingConnectedPeers = 62 | case AllPeers of 63 | [] -> []; 64 | _ -> 65 | [ 66 | ?LET(#peer{ connect_id = ConnectID }, oneof(AllPeers), 67 | begin 68 | [LPid, RPid] = 69 | [P#peer.pid || 70 | #{ peers := HostPeers } <- S#state.hosts, 71 | P = #peer{ connect_id = C } <- HostPeers, 72 | C =:= ConnectID], 73 | {call, enet_sync, disconnect, [LPid, RPid]} 74 | end), 75 | 76 | {call, enet_sync, send_unsequenced, 77 | [channel_pid(S), message_data()]}, 78 | 79 | {call, enet_sync, send_unreliable, 80 | [channel_pid(S), message_data()]}, 81 | 82 | {call, enet_sync, send_reliable, 83 | [channel_pid(S), message_data()]} 84 | ] 85 | end, 86 | AllCommands = AlwaysPossibleCommands ++ CommandsNeedingConnectedPeers, 87 | oneof(AllCommands). 88 | 89 | connect_command(S) -> 90 | ?LET({L, R}, {started_host(S), started_host(S)}, 91 | begin 92 | ChannelCount = channel_count(min(maps:get(channel_limit, L), 93 | maps:get(channel_limit, R))), 94 | #{ port := LPort, peer_limit := LPL, peer_count := LPC } = L, 95 | #{ port := RPort, peer_limit := RPL, peer_count := RPC } = R, 96 | if LPL =:= LPC -> 97 | {call, enet_sync, connect_from_full_local_host, 98 | [LPort, RPort, ChannelCount]}; 99 | RPL =:= RPC -> 100 | {call, enet_sync, connect_to_full_remote_host, 101 | [LPort, RPort, ChannelCount]}; 102 | LPort =:= RPort -> 103 | {call, enet_sync, connect_to_self, 104 | [LPort, RPort, ChannelCount]}; 105 | true -> 106 | {call, enet_sync, connect, [LPort, RPort, ChannelCount]} 107 | end 108 | end). 109 | 110 | 111 | %%% 112 | %%% Pre-conditions 113 | %%% 114 | 115 | precondition(S, {call, enet_sync, stop_host, [Port]}) -> 116 | case get_host_with_port(S, Port) of 117 | false -> false; 118 | _Host -> true 119 | end; 120 | 121 | precondition(S, {call, _, connect_from_full_local_host, [LPort, _R, _CC]}) -> 122 | case get_host_with_port(S, LPort) of 123 | false -> false; 124 | #{ peer_limit := Limit, peer_count := Count } -> Limit =:= Count 125 | end; 126 | 127 | precondition(S, {call, _, connect_to_full_remote_host, [LPort, RPort, _CC]}) -> 128 | LocalHost = get_host_with_port(S, LPort), 129 | RemoteHost = get_host_with_port(S, RPort), 130 | case {LocalHost, RemoteHost} of 131 | {#{ peer_limit := LLimit, peer_count := LCount }, 132 | #{ peer_limit := RLimit, peer_count := RCount }} -> 133 | LLimit > LCount andalso RLimit =:= RCount; 134 | {_, _} -> 135 | false 136 | end; 137 | 138 | precondition(_S, {call, _, connect_to_self, [LPort, RPort, _CC]}) -> 139 | LPort =:= RPort; 140 | 141 | precondition(S, {call, enet_sync, connect, [LPort, RPort, _ChannelCount]}) -> 142 | LocalHost = get_host_with_port(S, LPort), 143 | RemoteHost = get_host_with_port(S, RPort), 144 | case {LocalHost, RemoteHost} of 145 | {#{ peer_limit := LLimit, peer_count := LCount }, 146 | #{ peer_limit := RLimit, peer_count := RCount }} -> 147 | LLimit > LCount andalso RLimit > RCount; 148 | {_, _} -> 149 | false 150 | end; 151 | 152 | precondition(_S, {call, _, _, _}) -> 153 | true. 154 | 155 | 156 | 157 | %%% 158 | %%% State transitions 159 | %%% 160 | 161 | %% peer_count = 0, 162 | %% peers = [] 163 | 164 | next_state(S, V, {call, enet_sync, start_host, [_ConnectFun, Options]}) -> 165 | HostPort = {call, enet_sync, get_host_port, [V]}, 166 | {peer_limit, PeerLimit} = lists:keyfind(peer_limit, 1, Options), 167 | {channel_limit, ChannelLimit} = lists:keyfind(channel_limit, 1, Options), 168 | Host = #{ 169 | port => HostPort, 170 | peer_limit => PeerLimit, 171 | channel_limit => ChannelLimit, 172 | peer_count => 0, 173 | peers => [] 174 | }, 175 | S#state{ 176 | hosts = [Host | S#state.hosts] 177 | }; 178 | 179 | next_state(S, _V, {call, enet_sync, stop_host, [Port]}) -> 180 | #state{ hosts = Hosts } = S, 181 | {value, Host} = lists:search(fun(#{port := P}) -> P =:= Port end, Hosts), 182 | ConnectIDs = [CID || #peer{ connect_id = CID } <- maps:get(peers, Host)], 183 | TheOtherHosts = [H || H = #{ port := P } <- Hosts, P =/= Port], 184 | UpdatedHosts = 185 | lists:map( 186 | fun(H = #{ peers := Ps }) -> 187 | Peers = [P || P = #peer{ connect_id = CID} <- Ps, 188 | not lists:member(CID, ConnectIDs)], 189 | H#{ 190 | peer_count => length(Peers), 191 | peers => Peers 192 | } 193 | end, 194 | TheOtherHosts), 195 | S#state{ hosts = UpdatedHosts }; 196 | 197 | next_state(S, _V, {call, _, connect_from_full_local_host, [_L, _R, _CC]}) -> 198 | %% Trying to connect from a full host -> peer_limit_reached 199 | S; 200 | 201 | next_state(S, _V, {call, _, connect_to_full_remote_host, [_L, _R, _CC]}) -> 202 | %% Trying to connect to a full remote host -> timeout 203 | S; 204 | 205 | next_state(S, V, {call, _, connect_to_self, [Port, Port, ChannelCount]}) -> 206 | %% Trying to connect to own host 207 | case get_host_with_port(S, Port) of 208 | #{ peer_limit := L, peer_count := C } when L - C < 2 -> 209 | %% Trying to connect to own host without room for two new peers 210 | S; 211 | Host = #{ peer_count := PeerCount, peers := Peers } -> 212 | PeerPid = {call, enet_sync, get_local_peer_pid, [V]}, 213 | Channels = {call, enet_sync, get_local_channels, [V]}, 214 | RemotePeerPid = {call, enet_sync, get_remote_peer_pid, [V]}, 215 | RemoteChannels = {call, enet_sync, get_remote_channels, [V]}, 216 | ConnectID = {call, enet_peer, get_connect_id, [PeerPid]}, 217 | Peer1 = #peer{ 218 | connect_id = ConnectID, 219 | pid = PeerPid, 220 | channel_count = ChannelCount, 221 | channels = Channels 222 | }, 223 | Peer2 = #peer{ 224 | connect_id = ConnectID, 225 | pid = RemotePeerPid, 226 | channel_count = ChannelCount, 227 | channels = RemoteChannels 228 | }, 229 | NewHost = Host#{ 230 | peer_count => PeerCount + 2, 231 | peers => [Peer1, Peer2 | Peers] 232 | }, 233 | S#state{ 234 | hosts = replace_host_with_same_port(NewHost, S#state.hosts) 235 | } 236 | end; 237 | 238 | next_state(S, V, {call, enet_sync, connect, [LPort, RPort, ChannelCount]}) -> 239 | H1 = get_host_with_port(S, LPort), 240 | H2 = get_host_with_port(S, RPort), 241 | case {H1, H2} of 242 | {H1, H2 = #{}} -> 243 | PeerPid = {call, enet_sync, get_local_peer_pid, [V]}, 244 | Channels = {call, enet_sync, get_local_channels, [V]}, 245 | RemotePeerPid = {call, enet_sync, get_remote_peer_pid, [V]}, 246 | RemoteChannels = {call, enet_sync, get_remote_channels, [V]}, 247 | ConnectID = {call, enet_peer, get_connect_id, [PeerPid]}, 248 | Peer1 = #peer{ 249 | connect_id = ConnectID, 250 | pid = PeerPid, 251 | channel_count = ChannelCount, 252 | channels = Channels 253 | }, 254 | Peer2 = #peer{ 255 | connect_id = ConnectID, 256 | pid = RemotePeerPid, 257 | channel_count = ChannelCount, 258 | channels = RemoteChannels 259 | }, 260 | NewH1 = H1#{ 261 | peer_count => maps:get(peer_count, H1) + 1, 262 | peers => [Peer1 | maps:get(peers, H1)] 263 | }, 264 | NewH2 = H2#{ 265 | peer_count => maps:get(peer_count, H2) + 1, 266 | peers => [Peer2 | maps:get(peers, H2)] 267 | }, 268 | Hosts1 = replace_host_with_same_port(NewH1, S#state.hosts), 269 | Hosts2 = replace_host_with_same_port(NewH2, Hosts1), 270 | S#state{ 271 | hosts = Hosts2 272 | } 273 | end; 274 | 275 | next_state(S, _V, {call, enet_sync, disconnect, [LPid, _RPid]}) -> 276 | [ConnectID] = [ConnectID 277 | || #{ peers := Peers } <- S#state.hosts, 278 | #peer{ connect_id = ConnectID, pid = Pid } <- Peers, 279 | Pid =:= LPid], 280 | Hosts = lists:map( 281 | fun(H = #{ peers := Ps }) -> 282 | Peers = [P || P = #peer{ connect_id = C } <- Ps, 283 | C =/= ConnectID], 284 | H#{ 285 | peer_count => length(Peers), 286 | peers => Peers 287 | } 288 | end, 289 | S#state.hosts), 290 | S#state{ hosts = Hosts }; 291 | 292 | next_state(S, _V, {call, _, send_unsequenced, [_ChannelPid, _Data]}) -> 293 | S; 294 | 295 | next_state(S, _V, {call, _, send_unreliable, [_ChannelPid, _Data]}) -> 296 | S; 297 | 298 | next_state(S, _V, {call, _, send_reliable, [_ChannelPid, _Data]}) -> 299 | S. 300 | 301 | 302 | %%% 303 | %%% Post-conditions 304 | %%% 305 | 306 | postcondition(_S, {call, enet_sync, start_host, [_ConnectFun, _Opts]}, Res) -> 307 | case Res of 308 | {error, _Reason} -> false; 309 | {ok, _Port} -> true 310 | end; 311 | 312 | postcondition(S, {call, enet_sync, stop_host, [Port]}, Res) -> 313 | case lists:any(fun(#{port := P}) -> P =:= Port end, S#state.hosts) of 314 | false -> Res =:= {error, not_found}; 315 | true -> Res =:= ok 316 | end; 317 | 318 | postcondition(_S, {call, _, connect_from_full_local_host, [_L, _R, _C]}, Res) -> 319 | %% Tried to connect from a full host -> reached_peer_limit 320 | Res =:= {error, reached_peer_limit}; 321 | 322 | postcondition(_S, {call, _, connect_to_full_remote_host, [_L, _R, _C]}, Res) -> 323 | %% Tried to connect to a full remote host -> timeout 324 | Res =:= {error, local_timeout}; 325 | 326 | postcondition(S, {call, _, connect_to_self, [Port, Port, _C]}, Res) -> 327 | case get_host_with_port(S, Port) of 328 | #{ peer_limit := L, peer_count := L } -> 329 | %% Tried to connect from a full host -> reached_peer_limit 330 | Res =:= {error, reached_peer_limit}; 331 | #{ peer_limit := L, peer_count := C } when L - C =:= 1 -> 332 | %% Tried to connect to own host without room for two new peers 333 | case Res of 334 | {error, local_timeout} -> true; 335 | {error, reached_peer_limit} -> true; 336 | _ -> false 337 | end; 338 | #{} -> 339 | case Res of 340 | {_LPid, _LChannels, _RPid, _RChannels} -> true; 341 | {error, _Reason} -> false 342 | end 343 | end; 344 | 345 | postcondition(S, {call, enet_sync, connect, [LPort, RPort, _C]}, Res) -> 346 | H1 = get_host_with_port(S, LPort), 347 | H2 = get_host_with_port(S, RPort), 348 | case {H1, H2} of 349 | {#{}, #{}} -> 350 | case Res of 351 | {_LPid, _LChannels, _RPid, _RChannels} -> true; 352 | {error, _Reason} -> false 353 | end; 354 | {_H1, _H2} -> 355 | false 356 | end; 357 | 358 | postcondition(_S, {call, enet_sync, disconnect, [_LPeer, _RPeer]}, Res) -> 359 | Res =:= ok; 360 | 361 | postcondition(_S, {call, _, send_unsequenced, [_Channel, _Data]}, Res) -> 362 | Res =:= ok; 363 | 364 | postcondition(_S, {call, _, send_unreliable, [_Channel, _Data]}, Res) -> 365 | Res =:= ok; 366 | 367 | postcondition(_S, {call, _, send_reliable, [_Channel, _Data]}, Res) -> 368 | Res =:= ok. 369 | 370 | 371 | %%% 372 | %%% Properties 373 | %%% 374 | 375 | prop_sync_loopback() -> 376 | application:ensure_all_started(enet), 377 | ?FORALL(Commands, commands(?MODULE), 378 | ?WHENFAIL( 379 | pretty_print_commands(Commands), 380 | ?TRAPEXIT( 381 | begin 382 | {History, S, Res} = run_commands(?MODULE, Commands), 383 | lists:foreach( 384 | fun(#{ port := Port }) -> 385 | case enet_sync:stop_host(Port) of 386 | ok -> ok; 387 | {error, Reason} -> 388 | io:format("\n\nCleanup error: enet_sync:stop_host/1: ~p\n\n", [Reason]) 389 | end 390 | end, 391 | S#state.hosts), 392 | case Res of 393 | ok -> 394 | %% io:format("\nCommands: ~p\n\n", [Commands]), 395 | true; 396 | _ -> 397 | io:format("~nHistory: ~p~n~nState: ~p~n~nRes: ~p~n~n", 398 | [History, S, Res]), 399 | false 400 | end 401 | end))). 402 | 403 | 404 | %%% 405 | %%% Generators 406 | %%% 407 | 408 | connect_fun() -> 409 | oneof([symbolic_connect_fun(), connect_mfa()]). 410 | 411 | symbolic_connect_fun() -> 412 | {call, enet_model, mock_connect_fun, []}. 413 | 414 | connect_mfa() -> 415 | {enet_model, mock_start_worker, [{call, erlang, self, []}]}. 416 | 417 | mock_connect_fun() -> 418 | Self = self(), 419 | fun(PeerInfo) -> 420 | Self ! PeerInfo, 421 | {ok, Self} 422 | end. 423 | 424 | mock_start_worker(Self, PeerInfo) -> 425 | Self ! PeerInfo, 426 | {ok, Self}. 427 | 428 | busy_host_port(S = #state{}) -> 429 | ?LET(#{ port := Port }, started_host(S), 430 | Port). 431 | 432 | host_options() -> 433 | [{peer_limit, integer(1, 255)}, {channel_limit, integer(1, 8)}]. 434 | 435 | started_host(#state{ hosts = Hosts }) -> 436 | oneof(Hosts). 437 | 438 | host_port(#state{ hosts = Hosts }) -> 439 | oneof([Port || #{ port := Port } <- Hosts]). 440 | 441 | peer_pid(#state{ hosts = Hosts }) -> 442 | oneof([Pid || #{ peers := Peers } <- Hosts, 443 | #peer{ pid = Pid } <- Peers, 444 | Pid =/= undefined]). 445 | 446 | connect_id(#state{ hosts = Hosts }) -> 447 | oneof([ConnectID 448 | || #{ peers := Peers } <- Hosts, 449 | #peer{ connect_id = ConnectID, pid = undefined } <- Peers]). 450 | 451 | 452 | channel_pid(#state{ hosts = Hosts }) -> 453 | ?LET(#{ peers := Peers }, 454 | ?SUCHTHAT(#{ peers := Peers }, oneof(Hosts), 455 | Peers =/= []), 456 | ?LET(#peer{ channels = Channels, channel_count = Count }, oneof(Peers), 457 | ?LET(ID, integer(0, Count - 1), 458 | {call, enet_sync, get_channel, [ID, Channels]}))). 459 | 460 | 461 | channel_count(Limit) -> 462 | integer(1, Limit). 463 | 464 | local_ip() -> 465 | "127.0.0.1". 466 | 467 | message_data() -> 468 | binary(). 469 | 470 | 471 | %%% 472 | %%% Misc 473 | %%% 474 | 475 | replace_host_with_same_port(New = #{port := P}, [#{port := P} | Hosts]) -> 476 | [New | Hosts]; 477 | replace_host_with_same_port(New, [H | Hosts]) -> 478 | [H | replace_host_with_same_port(New, Hosts)]. 479 | 480 | get_host_with_port(#state{ hosts = Hosts }, Port) -> 481 | case lists:search(fun(#{port := P}) -> P =:= Port end, Hosts) of 482 | {value, Host} -> Host; 483 | false -> false 484 | end. 485 | 486 | pretty_print_commands(Commands) -> 487 | io:format("~n=TEST CASE=============================~n~n"), 488 | lists:foreach(fun (C) -> 489 | io:format(" ~s~n", [pprint(C)]) 490 | end, 491 | Commands), 492 | io:format("~n=======================================~n"). 493 | 494 | pprint({set, Var, Call}) -> 495 | io_lib:format("~s = ~s", [pprint(Var), pprint(Call)]); 496 | pprint({var, N}) -> 497 | io_lib:format("Var~p", [N]); 498 | pprint({call, M, F, Args}) -> 499 | PPArgs = [pprint(A) || A <- Args], 500 | io_lib:format("~p:~p(~s)", [M, F, lists:join(", ", PPArgs)]); 501 | pprint(Other) -> 502 | io_lib:format("~p", [Other]). 503 | -------------------------------------------------------------------------------- /test/enet_sync.erl: -------------------------------------------------------------------------------- 1 | -module(enet_sync). 2 | 3 | -include_lib("enet/include/enet.hrl"). 4 | 5 | -export([ 6 | start_host/2, 7 | connect_from_full_local_host/3, 8 | connect_to_full_remote_host/3, 9 | connect_to_self/3, 10 | connect/3, 11 | disconnect/2, 12 | stop_host/1, 13 | send_unsequenced/2, 14 | send_unreliable/2, 15 | send_reliable/2, 16 | get_host_port/1, 17 | get_local_peer_pid/1, 18 | get_local_channels/1, 19 | get_remote_peer_pid/1, 20 | get_remote_channels/1, 21 | get_channel/2 22 | ]). 23 | 24 | start_host(ConnectFun, Options) -> 25 | enet:start_host(0, ConnectFun, Options). 26 | 27 | connect_from_full_local_host(LocalHost, RemotePort, ChannelCount) -> 28 | connect(LocalHost, RemotePort, ChannelCount). 29 | 30 | connect_to_full_remote_host(LocalHost, RemotePort, ChannelCount) -> 31 | connect(LocalHost, RemotePort, ChannelCount). 32 | 33 | connect_to_self(LocalHost, RemotePort, ChannelCount) -> 34 | connect(LocalHost, RemotePort, ChannelCount). 35 | 36 | connect(LocalHost, RemotePort, ChannelCount) -> 37 | case enet:connect_peer(LocalHost, "127.0.0.1", RemotePort, ChannelCount) of 38 | {error, reached_peer_limit} -> {error, reached_peer_limit}; 39 | {ok, LPeer} -> 40 | receive 41 | #{peer := LP, channels := LCs, connect_id := CID} -> 42 | receive 43 | #{peer := RP, channels := RCs, connect_id := CID} -> 44 | {LP, LCs, RP, RCs} 45 | after 1000 -> 46 | {error, remote_timeout} 47 | end 48 | after 2000 -> 49 | Pool = enet_peer:get_pool(LPeer), 50 | Name = enet_peer:get_name(LPeer), 51 | exit(LPeer, normal), 52 | wait_until_worker_has_left_pool(Pool, Name), 53 | {error, local_timeout} 54 | end 55 | end. 56 | 57 | disconnect(LPid, RPid) -> 58 | LPool = enet_peer:get_pool(LPid), 59 | RPool = enet_peer:get_pool(RPid), 60 | LName = enet_peer:get_name(LPid), 61 | RName = enet_peer:get_name(RPid), 62 | ok = enet:disconnect_peer(LPid), 63 | receive 64 | {enet, disconnected, local, LPid, ConnectID} -> 65 | receive 66 | {enet, disconnected, remote, RPid, ConnectID} -> 67 | wait_until_worker_has_left_pool(LPool, LName), 68 | wait_until_worker_has_left_pool(RPool, RName) 69 | after 5000 -> 70 | {error, remote_timeout} 71 | end 72 | after 5000 -> 73 | {error, local_timeout} 74 | end. 75 | 76 | stop_host(Port) -> 77 | RemoteConnectedPeers = 78 | gproc:select([{{{p, l, remote_host_port}, '$1', Port}, [], ['$1']}]), 79 | PeerMonitors = lists:map(fun (Peer) -> 80 | Pool = enet_peer:get_pool(Peer), 81 | Name = enet_peer:get_name(Peer), 82 | {Peer, Pool, Name} 83 | end, 84 | RemoteConnectedPeers), 85 | [Pid] = gproc:select([{{{p, l, port}, '$1', Port}, [], ['$1']}]), 86 | Ref = monitor(process, Pid), 87 | ok = enet:stop_host(Port), 88 | receive 89 | {'DOWN', Ref, process, Pid, shutdown} -> 90 | lists:foreach(fun({Peer, Pool, _Name}) when Pool =:= Port -> 91 | exit(Peer, normal); 92 | ({Peer, Pool, Name}) -> 93 | exit(Peer, normal), 94 | wait_until_worker_has_left_pool(Pool, Name) 95 | end, 96 | PeerMonitors) 97 | after 1000 -> 98 | {error, timeout} 99 | end. 100 | 101 | send_unsequenced(Channel, Data) -> 102 | enet:send_unsequenced(Channel, Data), 103 | receive 104 | {enet, _ID, #unsequenced{ data = Data }} -> 105 | ok 106 | after 1000 -> 107 | {error, data_not_received} 108 | end. 109 | 110 | send_unreliable(Channel, Data) -> 111 | enet:send_unreliable(Channel, Data), 112 | receive 113 | {enet, _ID, #unreliable{ data = Data }} -> 114 | ok 115 | after 1000 -> 116 | {error, data_not_received} 117 | end. 118 | 119 | send_reliable(Channel, Data) -> 120 | enet:send_reliable(Channel, Data), 121 | receive 122 | {enet, _ID, #reliable{ data = Data }} -> 123 | ok 124 | after 1000 -> 125 | {error, data_not_received} 126 | end. 127 | 128 | 129 | %%% 130 | %%% Helpers 131 | %%% 132 | 133 | wait_until_worker_has_left_pool(Pool, Name) -> 134 | case lists:member(Name, [N || {N, _P} <- gproc_pool:worker_pool(Pool)]) of 135 | false -> ok; 136 | true -> 137 | receive 138 | after 200 -> wait_until_worker_has_left_pool(Pool, Name) 139 | end 140 | end. 141 | 142 | get_host_port(V) -> 143 | element(2, V). 144 | 145 | get_local_peer_pid(V) -> 146 | element(1, V). 147 | 148 | get_local_channels(V) -> 149 | element(2, V). 150 | 151 | get_remote_peer_pid(V) -> 152 | element(3, V). 153 | 154 | get_remote_channels(V) -> 155 | element(4, V). 156 | 157 | get_channel(ID, Channels) -> 158 | {ok, Channel} = maps:find(ID, Channels), 159 | Channel. 160 | --------------------------------------------------------------------------------