├── .clang-format ├── .github └── workflows │ ├── canary.yml │ ├── prebuild.yml │ └── test.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── NOTICE ├── README.md ├── binding.c ├── binding.js ├── lib ├── ip.js ├── network-interfaces.js ├── socket.js ├── stream.js └── udx.js ├── package.json └── test ├── all.js ├── bench ├── basic.js └── latency.js ├── helpers ├── index.js └── proxy.js ├── slow ├── stream-big-writes.js └── timeouts.js ├── socket.js ├── stream-framed.js ├── stream-parallel.js ├── stream-relay.js ├── stream.js └── udx.js /.clang-format: -------------------------------------------------------------------------------- 1 | AlignAfterOpenBracket: BlockIndent 2 | AlignConsecutiveMacros: Consecutive 3 | AlignEscapedNewlines: DontAlign 4 | AllowShortIfStatementsOnASingleLine: AllIfsAndElse 5 | AlwaysBreakAfterReturnType: TopLevel 6 | BinPackArguments: false 7 | BinPackParameters: false 8 | ContinuationIndentWidth: 2 9 | ColumnLimit: 0 10 | SpaceAfterCStyleCast: true 11 | SpaceBeforeParens: Custom 12 | SpaceBeforeParensOptions: 13 | AfterFunctionDeclarationName: true 14 | AfterFunctionDefinitionName: true 15 | -------------------------------------------------------------------------------- /.github/workflows/canary.yml: -------------------------------------------------------------------------------- 1 | name: Canary 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | jobs: 7 | canary: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - run: | 11 | curl -L -X POST \ 12 | -H "Accept: application/vnd.github+json" \ 13 | -H "Authorization: Bearer ${{ secrets.CANARY_DISPATCH_PAT }}" \ 14 | -H "X-GitHub-Api-Version: 2022-11-28" \ 15 | https://api.github.com/repos/holepunchto/canary-tests/dispatches \ 16 | -d '{"event_type":"triggered-by-${{ github.event.repository.name }}-${{ github.ref_name }}"}' 17 | -------------------------------------------------------------------------------- /.github/workflows/prebuild.yml: -------------------------------------------------------------------------------- 1 | name: Prebuild 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | prebuild: 6 | strategy: 7 | matrix: 8 | include: 9 | - os: ubuntu-22.04 10 | platform: linux 11 | arch: x64 12 | - os: ubuntu-22.04-arm64 13 | platform: linux 14 | arch: arm64 15 | - os: ubuntu-22.04 16 | platform: android 17 | arch: x64 18 | - os: ubuntu-22.04 19 | platform: android 20 | arch: ia32 21 | - os: ubuntu-22.04 22 | platform: android 23 | arch: arm64 24 | - os: ubuntu-22.04 25 | platform: android 26 | arch: arm 27 | - os: macos-14 28 | platform: darwin 29 | arch: x64 30 | - os: macos-14 31 | platform: darwin 32 | arch: arm64 33 | - os: macos-14 34 | platform: ios 35 | arch: arm64 36 | - os: macos-14 37 | platform: ios 38 | arch: arm64 39 | tags: -simulator 40 | flags: --simulator 41 | - os: macos-14 42 | platform: ios 43 | arch: x64 44 | tags: -simulator 45 | flags: --simulator 46 | - os: windows-2022 47 | platform: win32 48 | arch: x64 49 | - os: windows-2022 50 | platform: win32 51 | arch: arm64 52 | runs-on: ${{ matrix.os }} 53 | name: ${{ matrix.platform }}-${{ matrix.arch }}${{ matrix.tags }} 54 | steps: 55 | - uses: actions/checkout@v4 56 | - uses: actions/setup-node@v4 57 | with: 58 | node-version: lts/* 59 | - run: npm install -g bare-make 60 | - run: npm install 61 | - run: bare-make generate --platform ${{ matrix.platform }} --arch ${{ matrix.arch }} ${{ matrix.flags }} 62 | - run: bare-make build 63 | - run: bare-make install 64 | - uses: actions/upload-artifact@v4 65 | with: 66 | name: ${{ matrix.platform }}-${{ matrix.arch }}${{ matrix.tags }} 67 | path: prebuilds/* 68 | merge: 69 | needs: prebuild 70 | runs-on: ubuntu-latest 71 | steps: 72 | - uses: actions/download-artifact@v4 73 | with: 74 | path: prebuilds 75 | merge-multiple: true 76 | - uses: actions/upload-artifact@v4 77 | with: 78 | name: prebuilds 79 | path: prebuilds 80 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | branches: 8 | - main 9 | jobs: 10 | test: 11 | strategy: 12 | matrix: 13 | include: 14 | - os: ubuntu-latest 15 | platform: linux 16 | arch: x64 17 | - os: macos-latest 18 | platform: darwin 19 | arch: arm64 20 | - os: windows-latest 21 | platform: win32 22 | arch: x64 23 | runs-on: ${{ matrix.os }} 24 | name: ${{ matrix.platform }}-${{ matrix.arch }} 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: actions/setup-node@v4 28 | with: 29 | node-version: lts/* 30 | - run: npm install -g bare-runtime bare-make 31 | - run: npm install 32 | - run: bare-make generate --platform ${{ matrix.platform }} --arch ${{ matrix.arch }} 33 | - run: bare-make build 34 | - run: bare-make install 35 | - run: npm test 36 | - run: bare-make generate --platform ${{ matrix.platform }} --arch ${{ matrix.arch }} --debug --no-cache 37 | - run: bare-make build 38 | - run: bare-make install 39 | - run: npm test 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox 3 | build 4 | coverage 5 | package-lock.json 6 | prebuilds 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.25) 2 | 3 | find_package(cmake-bare REQUIRED PATHS node_modules/cmake-bare) 4 | find_package(cmake-fetch REQUIRED PATHS node_modules/cmake-fetch) 5 | find_package(cmake-napi REQUIRED PATHS node_modules/cmake-napi) 6 | 7 | project(udx_native C) 8 | 9 | bare_target(target) 10 | 11 | if(target MATCHES "win32") 12 | add_compile_options(/MT$<$:d>) 13 | endif() 14 | 15 | fetch_package("github:holepunchto/libudx#d81e1a8de9f8a2017b251dfcd8b1a66c0839ab20") 16 | 17 | add_bare_module(udx_native_bare) 18 | 19 | target_sources( 20 | ${udx_native_bare} 21 | PRIVATE 22 | binding.c 23 | ) 24 | 25 | target_link_libraries( 26 | ${udx_native_bare} 27 | PRIVATE 28 | $ 29 | PUBLIC 30 | udx 31 | ) 32 | 33 | if(target MATCHES "win32") 34 | target_link_libraries( 35 | ${udx_native_bare} 36 | PUBLIC 37 | ws2_32 38 | ) 39 | endif() 40 | 41 | add_napi_module(udx_native_node) 42 | 43 | target_sources( 44 | ${udx_native_node} 45 | PRIVATE 46 | binding.c 47 | ) 48 | 49 | target_link_libraries( 50 | ${udx_native_node} 51 | PRIVATE 52 | $ 53 | PUBLIC 54 | udx 55 | ) 56 | 57 | if(target MATCHES "win32") 58 | target_link_libraries( 59 | ${udx_native_node} 60 | PUBLIC 61 | ws2_32 62 | ) 63 | endif() 64 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Holepunch Inc 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # udx-native 2 | 3 | udx is reliable, multiplexed, and congestion-controlled streams over udp. 4 | 5 | ``` 6 | npm i udx-native 7 | ``` 8 | 9 | It's a transport protocol, made only for peer-to-peer networking. 10 | 11 | No handshakes. No encryption. No features. This is good for P2P.\ 12 | Just fast streams and messages that are composable into powerful things. 13 | 14 | ## Usage 15 | 16 | ```js 17 | const UDX = require('udx-native') 18 | 19 | const u = new UDX() 20 | const a = u.createSocket() 21 | const b = u.createSocket() 22 | 23 | b.on('message', function (message) { 24 | console.log('received', message.toString()) 25 | a.close() 26 | b.close() 27 | }) 28 | 29 | b.bind(0) 30 | a.send(Buffer.from('hello'), b.address().port) 31 | ``` 32 | 33 | ```js 34 | const UDX = require('udx-native') 35 | 36 | const u = new UDX() 37 | 38 | const socket1 = u.createSocket() 39 | const socket2 = u.createSocket() 40 | 41 | socket1.bind() 42 | socket2.bind() 43 | 44 | const stream1 = u.createStream(1) 45 | const stream2 = u.createStream(2) 46 | 47 | stream1.connect(socket1, stream2.id, socket2.address().port, '127.0.0.1') 48 | stream2.connect(socket2, stream1.id, socket1.address().port, '127.0.0.1') 49 | 50 | stream1.write(Buffer.from('hello')) 51 | stream1.end() 52 | 53 | stream2.on('data', function (data) { 54 | console.log(data) 55 | }) 56 | 57 | stream2.on('end', function () { 58 | stream2.end() 59 | }) 60 | 61 | stream1.on('close', function () { 62 | console.log('stream1 closed') 63 | socket1.close() 64 | }) 65 | 66 | stream2.on('close', function () { 67 | console.log('stream2 closed') 68 | socket2.close() 69 | }) 70 | ``` 71 | 72 | ## API 73 | 74 | #### `const udx = new UDX()` 75 | 76 | Creates a new UDX instance. 77 | 78 | #### `const bool = UDX.isIPv4(host)` 79 | 80 | Returns `true` if host is an IPv4 address. 81 | 82 | #### `const bool = UDX.isIPv6(host)` 83 | 84 | Returns `true` if host is an IPv6 address. 85 | 86 | #### `const family = UDX.isIP(host)` 87 | 88 | Returns the address family (`4` or `6`). Returns `0` if invalid. 89 | 90 | ## Sockets 91 | 92 | #### `const socket = udx.createSocket([options])` 93 | 94 | Creates a new socket instance. 95 | 96 | Available `options`: 97 | ```js 98 | { 99 | ipv6Only: false, 100 | reuseAddress: false 101 | } 102 | ``` 103 | 104 | #### `socket.udx` 105 | 106 | It's the UDX instance from where the socket was created. 107 | 108 | #### `socket.streams` 109 | 110 | It's a `Set` that tracks active streams (connected to the socket but not closed). 111 | 112 | #### `socket.userData` 113 | 114 | Optional custom userData. Default is `null`. 115 | 116 | #### `socket.bound` 117 | 118 | Indicates if it's bound to any port. It will be `true` after a successful `bind()`. 119 | 120 | #### `socket.closing` 121 | 122 | It will be `true` after `close()` is called. 123 | 124 | #### `socket.idle` 125 | 126 | Indicates that the socket doesn't have any connected stream. 127 | 128 | #### `socket.busy` 129 | 130 | Indicates that the socket have at least one connected stream. 131 | 132 | #### `socket.address()` 133 | 134 | Returns an object like `{ host, family, port }`. Only available after `bind()`. 135 | 136 | #### `socket.bind([port], [host])` 137 | 138 | The default port is `0`.\ 139 | If no host specified: it binds to IPv6 `::`. If fails then IPv4 `0.0.0.0`. 140 | 141 | #### `await socket.close()` 142 | 143 | It unbinds the socket so it stops listening for messages. 144 | 145 | #### `socket.setTTL(ttl)` 146 | 147 | Sets the amount of times that a packet is allowed to be forwarded through each router or gateway before being discarded. 148 | 149 | #### `socket.getRecvBufferSize()` 150 | #### `socket.setRecvBufferSize()` 151 | 152 | #### `socket.getSendBufferSize()` 153 | #### `socket.setSendBufferSize()` 154 | 155 | #### `await socket.send(buffer, port, [host], [ttl])` 156 | 157 | Sends a message to port and host destination. Default host is `127.0.0.1`. 158 | 159 | #### `socket.trySend(buffer, port, [host], [ttl])` 160 | 161 | Same behaviour as `send()` but no promise. 162 | 163 | #### `socket.on('message', (msg, from) => {})` 164 | 165 | `msg` is a buffer that containts the message.\ 166 | `from` is an object like `{ host, family, port }`. 167 | 168 | #### `socket.on('close', onclose)` 169 | 170 | Emitted if the socket was ever bound and it got closed. 171 | 172 | #### `socket.on('idle', onidle)` 173 | 174 | Emitted if the socket becomes idle (no active streams). 175 | 176 | #### `socket.on('busy', onbusy)` 177 | 178 | Emitted if the socket becomes busy (at least one active stream). 179 | 180 | #### `socket.on('listening', onlistening)` 181 | 182 | Emitted after a succesfull `bind()` call. 183 | 184 | ## Streams 185 | 186 | #### `const stream = udx.createStream(id, [options])` 187 | 188 | Creates a new stream instance that is a Duplex stream. 189 | 190 | Available `options`: 191 | ```js 192 | { 193 | firewall: (socket, port, host) => true, 194 | framed: false, 195 | seq: 0 196 | } 197 | ``` 198 | 199 | #### `stream.udx` 200 | 201 | It's the UDX instance from where the stream was created. 202 | 203 | #### `stream.socket` 204 | 205 | Refers to the socket that is connected to. Setted when you `connect()` the stream. 206 | 207 | #### `stream.id` 208 | 209 | Custom stream id. 210 | 211 | #### `stream.remoteId` 212 | 213 | Remote stream id. Setted when you `connect()` the stream. 214 | 215 | #### `stream.remoteId` 216 | 217 | Remote stream id. Setted when you `connect()` the stream. 218 | 219 | #### `stream.remoteHost` 220 | 221 | Remote host. Setted when you `connect()` the stream. 222 | 223 | #### `stream.remoteFamily` 224 | 225 | Remote family (`4` or `6`). Setted when you `connect()` the stream. 226 | 227 | #### `stream.remotePort` 228 | 229 | Remote port. Setted when you `connect()` the stream. 230 | 231 | #### `stream.userData` 232 | 233 | Optional custom userData. Default is `null`. 234 | 235 | #### `stream.connected` 236 | 237 | Indicates if the stream is connected to a socket. It becomes `false` if the stream is closed. 238 | 239 | #### `stream.mtu` 240 | 241 | Indicates the maximum size of each packet. 242 | 243 | #### `stream.rtt` 244 | #### `stream.cwnd` 245 | #### `stream.inflight` 246 | 247 | #### `stream.localHost` 248 | 249 | Indicates the connected socket host address. By default `null` if not connected. 250 | 251 | #### `stream.localFamily` 252 | 253 | Indicates the connected socket family address (`4` o `6`). By default `0` if not connected. 254 | 255 | #### `stream.localPort` 256 | 257 | Indicates the connected socket port. By default `0` if not connected. 258 | 259 | #### `stream.setInteractive(bool)` 260 | 261 | #### `stream.connect(socket, remoteId, port, [host], [options])` 262 | 263 | Connects the stream using a socket to a: remote stream id, and remote socket port/host. 264 | 265 | If no host specified it uses `127.0.0.1` by default. 266 | 267 | Available `options`: 268 | ```js 269 | { 270 | ack 271 | } 272 | ``` 273 | 274 | #### `await stream.changeRemote(remoteId, port, [host])` 275 | 276 | Change the remote end of the stream. 277 | 278 | If no host specified it uses `127.0.0.1` by default. 279 | 280 | #### `stream.relayTo(destination)` 281 | 282 | Relay stream to another stream. 283 | 284 | #### `await stream.send(buffer)` 285 | 286 | Send a message to another stream. Returns a promise. 287 | 288 | #### `stream.trySend(buffer)` 289 | 290 | Send a message to another stream. 291 | 292 | #### `const drained = await stream.flush()` 293 | 294 | Wait for pending stream writes to have been explicitly acknowledged by the other side of the connection. 295 | 296 | #### `stream.on('connect', onconnect)` 297 | 298 | Emitted after the stream is connected to a socket. 299 | 300 | #### `stream.on('message', onmessage)` 301 | 302 | Emitted if the stream receives a message. 303 | 304 | #### `stream.on('remote-changed', onremotechanged)` 305 | 306 | Emitted when the remote end of the stream changes. 307 | 308 | #### `stream.on('mtu-exceeded', onmtuexceeded)` 309 | 310 | Emitted only once if you write data that exceeds the MTU. 311 | 312 | ## Network interfaces 313 | 314 | #### `const interfaces = udx.networkInterfaces()` 315 | 316 | Returns an array of network interfaces, for example: 317 | ```js 318 | [ 319 | { name: 'lo', host: '127.0.0.1', family: 4, internal: true }, 320 | { name: 'enp4s0', host: '192.168.0.20', family: 4, internal: false }, 321 | { name: 'lo', host: '::1', family: 6, internal: true }, 322 | { name: 'enp4s0', host: 'df08::c8df:bf61:95c1:352b', family: 6, internal: false } 323 | ] 324 | ``` 325 | 326 | #### `const watcher = udx.watchNetworkInterfaces([onchange])` 327 | 328 | Listens to changes in the network interfaces. The `watcher` object is iterable. 329 | 330 | #### `watcher.interfaces` 331 | 332 | Array of network interfaces. 333 | 334 | #### `watcher.watch()` 335 | 336 | Starts watching for changes. By default it already does it. This is only useful after you `unwatch()`. 337 | 338 | #### `watcher.unwatch()` 339 | 340 | Stops watching for changes. 341 | 342 | #### `await watcher.destroy()` 343 | 344 | Closes the watcher. 345 | 346 | #### `watcher.on('change', onchange)` 347 | 348 | Emitted after a network interface change. 349 | 350 | #### `watcher.on('close', onclose)` 351 | 352 | Emitted after the watcher is closed. 353 | 354 | ## DNS 355 | 356 | #### `const address = await udx.lookup(host, [options])` 357 | 358 | It does a DNS lookup for the IP address. Returns `{ host, family }`. 359 | 360 | Available `options`: 361 | ```js 362 | { 363 | family: 0 // => 0, 4 or 6 364 | } 365 | ``` 366 | 367 | ## Dev Setup 368 | 369 | To develop UDX locally, you need to create a libudx prebuild. [bare-make](https://github.com/holepunchto/bare-make) is used for this. 370 | 371 | Requirements: The Clang C-compiler should be installed. 372 | 373 | The other setup steps are: 374 | 375 | - `npm install -g bare-runtime bare-make` 376 | - `npm install` 377 | - `bare-make generate` 378 | - `bare-make build` 379 | - `bare-make install` 380 | 381 | When testing changes, rebuild the prebuilds: 382 | 383 | - `bare-make generate` 384 | - `bare-make build` 385 | - `bare-make install` 386 | 387 | 388 | ## License 389 | 390 | Apache-2.0 391 | -------------------------------------------------------------------------------- /binding.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define UDX_NAPI_INTERACTIVE 0 8 | #define UDX_NAPI_NON_INTERACTIVE 1 9 | #define UDX_NAPI_FRAMED 2 10 | 11 | typedef struct { 12 | udx_t udx; 13 | 14 | char *read_buf; 15 | size_t read_buf_free; 16 | 17 | napi_async_cleanup_hook_handle teardown; 18 | bool exiting; 19 | bool has_teardown; 20 | } udx_napi_t; 21 | 22 | typedef struct { 23 | udx_socket_t socket; 24 | udx_napi_t *udx; 25 | 26 | napi_env env; 27 | napi_ref ctx; 28 | napi_ref on_send; 29 | napi_ref on_message; 30 | napi_ref on_close; 31 | napi_ref realloc_message; 32 | } udx_napi_socket_t; 33 | 34 | typedef struct { 35 | udx_stream_t stream; 36 | udx_napi_t *udx; 37 | 38 | int mode; 39 | 40 | char *read_buf; 41 | char *read_buf_head; 42 | size_t read_buf_free; 43 | 44 | ssize_t frame_len; 45 | 46 | napi_env env; 47 | napi_ref ctx; 48 | napi_ref on_data; 49 | napi_ref on_end; 50 | napi_ref on_drain; 51 | napi_ref on_ack; 52 | napi_ref on_send; 53 | napi_ref on_message; 54 | napi_ref on_close; 55 | napi_ref on_firewall; 56 | napi_ref on_remote_changed; 57 | napi_ref realloc_data; 58 | napi_ref realloc_message; 59 | } udx_napi_stream_t; 60 | 61 | typedef struct { 62 | udx_lookup_t handle; 63 | udx_napi_t *udx; 64 | 65 | char *host; 66 | 67 | napi_env env; 68 | napi_ref ctx; 69 | napi_ref on_lookup; 70 | } udx_napi_lookup_t; 71 | 72 | typedef struct { 73 | udx_interface_event_t handle; 74 | udx_napi_t *udx; 75 | 76 | napi_env env; 77 | napi_ref ctx; 78 | napi_ref on_event; 79 | napi_ref on_close; 80 | } udx_napi_interface_event_t; 81 | 82 | inline static void 83 | parse_address (struct sockaddr *name, char *ip, size_t size, int *port, int *family) { 84 | if (name->sa_family == AF_INET) { 85 | *port = ntohs(((struct sockaddr_in *) name)->sin_port); 86 | *family = 4; 87 | uv_ip4_name((struct sockaddr_in *) name, ip, size); 88 | } else if (name->sa_family == AF_INET6) { 89 | *port = ntohs(((struct sockaddr_in6 *) name)->sin6_port); 90 | *family = 6; 91 | uv_ip6_name((struct sockaddr_in6 *) name, ip, size); 92 | } 93 | } 94 | 95 | static void 96 | on_udx_send (udx_socket_send_t *req, int status) { 97 | udx_napi_socket_t *n = (udx_napi_socket_t *) req->socket; 98 | if (n->udx->exiting) return; 99 | 100 | napi_env env = n->env; 101 | 102 | napi_handle_scope scope; 103 | napi_open_handle_scope(env, &scope); 104 | 105 | napi_value ctx; 106 | napi_get_reference_value(env, n->ctx, &ctx); 107 | 108 | napi_value callback; 109 | napi_get_reference_value(env, n->on_send, &callback); 110 | 111 | napi_value argv[2]; 112 | napi_create_int32(env, (uintptr_t) req->data, &(argv[0])); 113 | napi_create_int32(env, status, &(argv[1])); 114 | 115 | if (napi_make_callback(env, NULL, ctx, callback, 2, argv, NULL) == napi_pending_exception) { 116 | napi_value fatal_exception; 117 | napi_get_and_clear_last_exception(env, &fatal_exception); 118 | napi_fatal_exception(env, fatal_exception); 119 | } 120 | 121 | napi_close_handle_scope(env, scope); 122 | } 123 | 124 | static void 125 | on_udx_message (udx_socket_t *self, ssize_t read_len, const uv_buf_t *buf, const struct sockaddr *from) { 126 | udx_napi_socket_t *n = (udx_napi_socket_t *) self; 127 | if (n->udx->exiting) return; 128 | 129 | int port = 0; 130 | char ip[INET6_ADDRSTRLEN]; 131 | int family = 0; 132 | parse_address((struct sockaddr *) from, ip, INET6_ADDRSTRLEN, &port, &family); 133 | 134 | if (buf->len > n->udx->read_buf_free) return; 135 | 136 | memcpy(n->udx->read_buf, buf->base, buf->len); 137 | 138 | napi_env env = n->env; 139 | 140 | napi_handle_scope scope; 141 | napi_open_handle_scope(env, &scope); 142 | 143 | napi_value ctx; 144 | napi_get_reference_value(env, n->ctx, &ctx); 145 | 146 | napi_value callback; 147 | napi_get_reference_value(env, n->on_message, &callback); 148 | 149 | napi_value argv[4]; 150 | napi_create_uint32(env, read_len, &(argv[0])); 151 | napi_create_uint32(env, port, &(argv[1])); 152 | napi_create_string_utf8(env, ip, NAPI_AUTO_LENGTH, &(argv[2])); 153 | napi_create_uint32(env, family, &(argv[3])); 154 | 155 | napi_value res; 156 | 157 | if (napi_make_callback(env, NULL, ctx, callback, 4, argv, &res) == napi_pending_exception) { 158 | napi_value fatal_exception; 159 | napi_get_and_clear_last_exception(env, &fatal_exception); 160 | napi_fatal_exception(env, fatal_exception); 161 | 162 | // avoid reentry 163 | if (!(n->udx->exiting)) { 164 | napi_env env = n->env; 165 | 166 | napi_handle_scope scope; 167 | napi_open_handle_scope(env, &scope); 168 | 169 | napi_value ctx; 170 | napi_get_reference_value(env, n->ctx, &ctx); 171 | 172 | napi_value callback; 173 | napi_get_reference_value(env, n->realloc_message, &callback); 174 | 175 | if (napi_make_callback(env, NULL, ctx, callback, 0, NULL, &res) == napi_pending_exception) { 176 | napi_value fatal_exception; 177 | napi_get_and_clear_last_exception(env, &fatal_exception); 178 | napi_fatal_exception(env, fatal_exception); 179 | } 180 | 181 | napi_get_buffer_info(env, res, (void **) &(n->udx->read_buf), &(n->udx->read_buf_free)); 182 | 183 | napi_close_handle_scope(env, scope); 184 | } 185 | } else { 186 | napi_get_buffer_info(env, res, (void **) &(n->udx->read_buf), &(n->udx->read_buf_free)); 187 | } 188 | 189 | napi_close_handle_scope(env, scope); 190 | } 191 | 192 | static void 193 | on_udx_close (udx_socket_t *self) { 194 | udx_napi_socket_t *n = (udx_napi_socket_t *) self; 195 | 196 | napi_env env = n->env; 197 | 198 | if (!(n->udx->exiting)) { 199 | napi_handle_scope scope; 200 | napi_open_handle_scope(env, &scope); 201 | 202 | napi_value ctx; 203 | napi_get_reference_value(env, n->ctx, &ctx); 204 | 205 | napi_value callback; 206 | napi_get_reference_value(env, n->on_close, &callback); 207 | 208 | if (napi_make_callback(env, NULL, ctx, callback, 0, NULL, NULL) == napi_pending_exception) { 209 | napi_value fatal_exception; 210 | napi_get_and_clear_last_exception(env, &fatal_exception); 211 | napi_fatal_exception(env, fatal_exception); 212 | } 213 | 214 | napi_close_handle_scope(env, scope); 215 | } 216 | 217 | napi_delete_reference(env, n->on_send); 218 | napi_delete_reference(env, n->on_message); 219 | napi_delete_reference(env, n->on_close); 220 | napi_delete_reference(env, n->realloc_message); 221 | napi_delete_reference(env, n->ctx); 222 | } 223 | 224 | static void 225 | on_udx_teardown (napi_async_cleanup_hook_handle handle, void *data) { 226 | udx_napi_t *self = (udx_napi_t *) data; 227 | udx_t *udx = (udx_t *) data; 228 | 229 | self->exiting = true; 230 | udx_teardown(udx); 231 | } 232 | 233 | static void 234 | ensure_teardown (napi_env env, udx_napi_t *udx) { 235 | if (udx->has_teardown) return; 236 | udx->has_teardown = true; 237 | napi_add_async_cleanup_hook(env, on_udx_teardown, (void *) udx, &(udx->teardown)); 238 | } 239 | 240 | static void 241 | on_udx_stream_end (udx_stream_t *stream) { 242 | udx_napi_stream_t *n = (udx_napi_stream_t *) stream; 243 | if (n->udx->exiting) return; 244 | 245 | size_t read = n->read_buf_head - n->read_buf; 246 | 247 | napi_env env = n->env; 248 | 249 | napi_handle_scope scope; 250 | napi_open_handle_scope(env, &scope); 251 | 252 | napi_value ctx; 253 | napi_get_reference_value(env, n->ctx, &ctx); 254 | 255 | napi_value callback; 256 | napi_get_reference_value(env, n->on_end, &callback); 257 | 258 | napi_value argv[1]; 259 | napi_create_uint32(env, read, &(argv[0])); 260 | 261 | if (napi_make_callback(env, NULL, ctx, callback, 1, argv, NULL) == napi_pending_exception) { 262 | napi_value fatal_exception; 263 | napi_get_and_clear_last_exception(env, &fatal_exception); 264 | napi_fatal_exception(env, fatal_exception); 265 | } 266 | 267 | napi_close_handle_scope(env, scope); 268 | } 269 | 270 | static void 271 | on_udx_stream_read (udx_stream_t *stream, ssize_t read_len, const uv_buf_t *buf) { 272 | if (read_len == UV_EOF) return on_udx_stream_end(stream); 273 | 274 | udx_napi_stream_t *n = (udx_napi_stream_t *) stream; 275 | if (n->udx->exiting) return; 276 | 277 | // ignore the message if it doesn't fit in the read buffer 278 | if (buf->len > n->read_buf_free) return; 279 | 280 | if (n->mode == UDX_NAPI_FRAMED && n->frame_len == -1) { 281 | if (buf->len < 3) { 282 | n->mode = UDX_NAPI_INTERACTIVE; 283 | } else { 284 | uint8_t *b = (uint8_t *) buf->base; 285 | n->frame_len = 3 + (b[0] | (b[1] << 8) | (b[2] << 16)); 286 | } 287 | } 288 | 289 | memcpy(n->read_buf_head, buf->base, buf->len); 290 | 291 | n->read_buf_head += buf->len; 292 | n->read_buf_free -= buf->len; 293 | 294 | if (n->mode == UDX_NAPI_NON_INTERACTIVE && n->read_buf_free >= 2 * stream->mtu) { 295 | return; 296 | } 297 | 298 | ssize_t read = n->read_buf_head - n->read_buf; 299 | 300 | if (n->mode == UDX_NAPI_FRAMED) { 301 | if (n->frame_len < read) { 302 | n->mode = UDX_NAPI_INTERACTIVE; 303 | } else if (n->frame_len == read) { 304 | n->frame_len = -1; 305 | } else if (n->read_buf_free < 2 * stream->mtu) { 306 | n->frame_len -= read; 307 | } else { 308 | return; // wait for more data 309 | } 310 | } 311 | 312 | napi_env env = n->env; 313 | 314 | napi_handle_scope scope; 315 | napi_open_handle_scope(env, &scope); 316 | 317 | napi_value ctx; 318 | napi_get_reference_value(env, n->ctx, &ctx); 319 | 320 | napi_value callback; 321 | napi_get_reference_value(env, n->on_data, &callback); 322 | 323 | napi_value argv[1]; 324 | napi_create_uint32(env, read, &(argv[0])); 325 | 326 | napi_value res; 327 | 328 | if (napi_make_callback(env, NULL, ctx, callback, 1, argv, &res) == napi_pending_exception) { 329 | napi_value fatal_exception; 330 | napi_get_and_clear_last_exception(env, &fatal_exception); 331 | napi_fatal_exception(env, fatal_exception); 332 | 333 | // avoid re-entry 334 | if (!(n->udx->exiting)) { 335 | napi_handle_scope scope; 336 | napi_open_handle_scope(env, &scope); 337 | 338 | napi_value ctx; 339 | napi_get_reference_value(env, n->ctx, &ctx); 340 | 341 | napi_value callback; 342 | napi_get_reference_value(env, n->realloc_data, &callback); 343 | 344 | if (napi_make_callback(env, NULL, ctx, callback, 0, NULL, &res) == napi_pending_exception) { 345 | napi_value fatal_exception; 346 | napi_get_and_clear_last_exception(env, &fatal_exception); 347 | napi_fatal_exception(env, fatal_exception); 348 | } 349 | 350 | napi_get_buffer_info(env, res, (void **) &(n->read_buf), &(n->read_buf_free)); 351 | n->read_buf_head = n->read_buf; 352 | 353 | napi_close_handle_scope(env, scope); 354 | } 355 | } else { 356 | napi_get_buffer_info(env, res, (void **) &(n->read_buf), &(n->read_buf_free)); 357 | n->read_buf_head = n->read_buf; 358 | } 359 | 360 | napi_close_handle_scope(env, scope); 361 | } 362 | 363 | static void 364 | on_udx_stream_drain (udx_stream_t *stream) { 365 | udx_napi_stream_t *n = (udx_napi_stream_t *) stream; 366 | if (n->udx->exiting) return; 367 | 368 | napi_env env = n->env; 369 | 370 | napi_handle_scope scope; 371 | napi_open_handle_scope(env, &scope); 372 | 373 | napi_value ctx; 374 | napi_get_reference_value(env, n->ctx, &ctx); 375 | 376 | napi_value callback; 377 | napi_get_reference_value(env, n->on_drain, &callback); 378 | 379 | if (napi_make_callback(env, NULL, ctx, callback, 0, NULL, NULL) == napi_pending_exception) { 380 | napi_value fatal_exception; 381 | napi_get_and_clear_last_exception(env, &fatal_exception); 382 | napi_fatal_exception(env, fatal_exception); 383 | } 384 | 385 | napi_close_handle_scope(env, scope); 386 | } 387 | 388 | static void 389 | on_udx_stream_ack (udx_stream_write_t *req, int status, int unordered) { 390 | udx_napi_stream_t *n = (udx_napi_stream_t *) req->stream; 391 | if (n->udx->exiting) return; 392 | 393 | napi_env env = n->env; 394 | 395 | napi_handle_scope scope; 396 | napi_open_handle_scope(env, &scope); 397 | 398 | napi_value ctx; 399 | napi_get_reference_value(env, n->ctx, &ctx); 400 | 401 | napi_value callback; 402 | napi_get_reference_value(env, n->on_ack, &callback); 403 | 404 | napi_value argv[1]; 405 | napi_create_uint32(env, (uintptr_t) req->data, &(argv[0])); 406 | 407 | if (napi_make_callback(env, NULL, ctx, callback, 1, argv, NULL) == napi_pending_exception) { 408 | napi_value fatal_exception; 409 | napi_get_and_clear_last_exception(env, &fatal_exception); 410 | napi_fatal_exception(env, fatal_exception); 411 | } 412 | 413 | napi_close_handle_scope(env, scope); 414 | } 415 | 416 | static void 417 | on_udx_stream_send (udx_stream_send_t *req, int status) { 418 | udx_napi_stream_t *n = (udx_napi_stream_t *) req->stream; 419 | if (n->udx->exiting) return; 420 | 421 | napi_env env = n->env; 422 | 423 | napi_handle_scope scope; 424 | napi_open_handle_scope(env, &scope); 425 | 426 | napi_value ctx; 427 | napi_get_reference_value(env, n->ctx, &ctx); 428 | 429 | napi_value callback; 430 | napi_get_reference_value(env, n->on_send, &callback); 431 | 432 | napi_value argv[2]; 433 | napi_create_int32(env, (uintptr_t) req->data, &(argv[0])); 434 | napi_create_int32(env, status, &(argv[1])); 435 | 436 | if (napi_make_callback(env, NULL, ctx, callback, 2, argv, NULL) == napi_pending_exception) { 437 | napi_value fatal_exception; 438 | napi_get_and_clear_last_exception(env, &fatal_exception); 439 | napi_fatal_exception(env, fatal_exception); 440 | } 441 | 442 | napi_close_handle_scope(env, scope); 443 | } 444 | 445 | static void 446 | on_udx_stream_recv (udx_stream_t *stream, ssize_t read_len, const uv_buf_t *buf) { 447 | udx_napi_stream_t *n = (udx_napi_stream_t *) stream; 448 | if (n->udx->exiting) return; 449 | 450 | if (buf->len > n->udx->read_buf_free) return; 451 | 452 | memcpy(n->udx->read_buf, buf->base, buf->len); 453 | 454 | napi_env env = n->env; 455 | 456 | napi_handle_scope scope; 457 | napi_open_handle_scope(env, &scope); 458 | 459 | napi_value ctx; 460 | napi_get_reference_value(env, n->ctx, &ctx); 461 | 462 | napi_value callback; 463 | napi_get_reference_value(env, n->on_message, &callback); 464 | 465 | napi_value argv[1]; 466 | napi_create_uint32(env, read_len, &(argv[0])); 467 | 468 | napi_value res; 469 | 470 | if (napi_make_callback(env, NULL, ctx, callback, 1, argv, &res) == napi_pending_exception) { 471 | napi_value fatal_exception; 472 | napi_get_and_clear_last_exception(env, &fatal_exception); 473 | napi_fatal_exception(env, fatal_exception); 474 | 475 | // avoid re-entry 476 | if (!(n->udx->exiting)) { 477 | napi_handle_scope scope; 478 | napi_open_handle_scope(env, &scope); 479 | 480 | napi_value ctx; 481 | napi_get_reference_value(env, n->ctx, &ctx); 482 | 483 | napi_value callback; 484 | napi_get_reference_value(env, n->realloc_message, &callback); 485 | 486 | if (napi_make_callback(env, NULL, ctx, callback, 0, NULL, &res) == napi_pending_exception) { 487 | napi_value fatal_exception; 488 | napi_get_and_clear_last_exception(env, &fatal_exception); 489 | napi_fatal_exception(env, fatal_exception); 490 | } 491 | 492 | napi_get_buffer_info(env, res, (void **) &(n->udx->read_buf), &(n->udx->read_buf_free)); 493 | 494 | napi_close_handle_scope(env, scope); 495 | } 496 | } else { 497 | napi_get_buffer_info(env, res, (void **) &(n->udx->read_buf), &(n->udx->read_buf_free)); 498 | } 499 | 500 | napi_close_handle_scope(env, scope); 501 | } 502 | 503 | static void 504 | on_udx_stream_finalize (udx_stream_t *stream) { 505 | udx_napi_stream_t *n = (udx_napi_stream_t *) stream; 506 | 507 | napi_delete_reference(n->env, n->on_data); 508 | napi_delete_reference(n->env, n->on_end); 509 | napi_delete_reference(n->env, n->on_drain); 510 | napi_delete_reference(n->env, n->on_ack); 511 | napi_delete_reference(n->env, n->on_send); 512 | napi_delete_reference(n->env, n->on_message); 513 | napi_delete_reference(n->env, n->on_close); 514 | napi_delete_reference(n->env, n->on_firewall); 515 | napi_delete_reference(n->env, n->on_remote_changed); 516 | napi_delete_reference(n->env, n->realloc_data); 517 | napi_delete_reference(n->env, n->realloc_message); 518 | napi_delete_reference(n->env, n->ctx); 519 | } 520 | 521 | static void 522 | on_udx_stream_close (udx_stream_t *stream, int status) { 523 | udx_napi_stream_t *n = (udx_napi_stream_t *) stream; 524 | if (n->udx->exiting) return; 525 | 526 | napi_env env = n->env; 527 | 528 | napi_handle_scope scope; 529 | napi_open_handle_scope(env, &scope); 530 | 531 | napi_value ctx; 532 | napi_get_reference_value(env, n->ctx, &ctx); 533 | 534 | napi_value callback; 535 | napi_get_reference_value(env, n->on_close, &callback); 536 | 537 | napi_value argv[1]; 538 | 539 | if (status >= 0) { 540 | napi_get_null(env, &(argv[0])); 541 | } else { 542 | napi_value code; 543 | napi_value msg; 544 | napi_create_string_utf8(env, uv_err_name(status), NAPI_AUTO_LENGTH, &code); 545 | napi_create_string_utf8(env, uv_strerror(status), NAPI_AUTO_LENGTH, &msg); 546 | napi_create_error(env, code, msg, &(argv[0])); 547 | } 548 | 549 | if (napi_make_callback(env, NULL, ctx, callback, 1, argv, NULL) == napi_pending_exception) { 550 | napi_value fatal_exception; 551 | napi_get_and_clear_last_exception(env, &fatal_exception); 552 | napi_fatal_exception(env, fatal_exception); 553 | } 554 | 555 | napi_close_handle_scope(env, scope); 556 | } 557 | 558 | static int 559 | on_udx_stream_firewall (udx_stream_t *stream, udx_socket_t *socket, const struct sockaddr *from) { 560 | udx_napi_stream_t *n = (udx_napi_stream_t *) stream; 561 | udx_napi_socket_t *s = (udx_napi_socket_t *) socket; 562 | 563 | uint32_t fw = 1; // assume error means firewall it, whilst reporting the uncaught 564 | if (n->udx->exiting) return fw; 565 | 566 | int port = 0; 567 | char ip[INET6_ADDRSTRLEN]; 568 | int family = 0; 569 | parse_address((struct sockaddr *) from, ip, INET6_ADDRSTRLEN, &port, &family); 570 | 571 | napi_env env = n->env; 572 | 573 | napi_handle_scope scope; 574 | napi_open_handle_scope(env, &scope); 575 | 576 | napi_value ctx; 577 | napi_get_reference_value(env, n->ctx, &ctx); 578 | 579 | napi_value callback; 580 | napi_get_reference_value(env, n->on_firewall, &callback); 581 | 582 | napi_value res; 583 | napi_value argv[4]; 584 | 585 | napi_get_reference_value(env, s->ctx, &(argv[0])); 586 | napi_create_uint32(env, port, &(argv[1])); 587 | napi_create_string_utf8(env, ip, NAPI_AUTO_LENGTH, &(argv[2])); 588 | napi_create_uint32(env, family, &(argv[3])); 589 | 590 | if (napi_make_callback(env, NULL, ctx, callback, 4, argv, &res) == napi_pending_exception) { 591 | napi_value fatal_exception; 592 | napi_get_and_clear_last_exception(env, &fatal_exception); 593 | napi_fatal_exception(env, fatal_exception); 594 | } else { 595 | napi_get_value_uint32(env, res, &fw); 596 | } 597 | 598 | napi_close_handle_scope(env, scope); 599 | 600 | return fw; 601 | } 602 | 603 | static void 604 | on_udx_stream_remote_changed (udx_stream_t *stream) { 605 | udx_napi_stream_t *n = (udx_napi_stream_t *) stream; 606 | if (n->udx->exiting) return; 607 | 608 | napi_env env = n->env; 609 | 610 | napi_handle_scope scope; 611 | napi_open_handle_scope(env, &scope); 612 | 613 | napi_value ctx; 614 | napi_get_reference_value(env, n->ctx, &ctx); 615 | 616 | napi_value callback; 617 | napi_get_reference_value(env, n->on_remote_changed, &callback); 618 | 619 | if (napi_make_callback(env, NULL, ctx, callback, 0, NULL, NULL) == napi_pending_exception) { 620 | napi_value fatal_exception; 621 | napi_get_and_clear_last_exception(env, &fatal_exception); 622 | napi_fatal_exception(env, fatal_exception); 623 | } 624 | 625 | napi_close_handle_scope(env, scope); 626 | } 627 | 628 | static void 629 | on_udx_lookup (udx_lookup_t *lookup, int status, const struct sockaddr *addr, int addr_len) { 630 | udx_napi_lookup_t *n = (udx_napi_lookup_t *) lookup; 631 | if (n->udx->exiting) return; 632 | 633 | napi_env env = n->env; 634 | 635 | char ip[INET6_ADDRSTRLEN] = ""; 636 | int family = 0; 637 | 638 | napi_handle_scope scope; 639 | napi_open_handle_scope(env, &scope); 640 | 641 | napi_value ctx; 642 | napi_get_reference_value(env, n->ctx, &ctx); 643 | 644 | napi_value callback; 645 | napi_get_reference_value(env, n->on_lookup, &callback); 646 | 647 | if (status >= 0) { 648 | if (addr->sa_family == AF_INET) { 649 | uv_ip4_name((struct sockaddr_in *) addr, ip, addr_len); 650 | family = 4; 651 | } else if (addr->sa_family == AF_INET6) { 652 | uv_ip6_name((struct sockaddr_in6 *) addr, ip, addr_len); 653 | family = 6; 654 | } 655 | 656 | napi_value argv[3]; 657 | napi_get_null(env, &(argv[0])); 658 | napi_create_string_utf8(env, ip, NAPI_AUTO_LENGTH, &(argv[1])); 659 | napi_create_uint32(env, family, &(argv[2])); 660 | 661 | if (napi_make_callback(env, NULL, ctx, callback, 3, argv, NULL) == napi_pending_exception) { 662 | napi_value fatal_exception; 663 | napi_get_and_clear_last_exception(env, &fatal_exception); 664 | napi_fatal_exception(env, fatal_exception); 665 | } 666 | } else { 667 | napi_value argv[1]; 668 | napi_value code; 669 | napi_value msg; 670 | napi_create_string_utf8(env, uv_err_name(status), NAPI_AUTO_LENGTH, &code); 671 | napi_create_string_utf8(env, uv_strerror(status), NAPI_AUTO_LENGTH, &msg); 672 | napi_create_error(env, code, msg, &(argv[0])); 673 | 674 | if (napi_make_callback(env, NULL, ctx, callback, 1, argv, NULL) == napi_pending_exception) { 675 | napi_value fatal_exception; 676 | napi_get_and_clear_last_exception(env, &fatal_exception); 677 | napi_fatal_exception(env, fatal_exception); 678 | } 679 | } 680 | 681 | free(n->host); 682 | 683 | napi_close_handle_scope(env, scope); 684 | 685 | napi_delete_reference(n->env, n->on_lookup); 686 | napi_delete_reference(n->env, n->ctx); 687 | } 688 | 689 | static void 690 | on_udx_interface_event (udx_interface_event_t *handle, int status) { 691 | udx_napi_interface_event_t *e = (udx_napi_interface_event_t *) handle; 692 | if (e->udx->exiting) return; 693 | 694 | napi_env env = e->env; 695 | 696 | napi_handle_scope scope; 697 | napi_open_handle_scope(env, &scope); 698 | 699 | napi_value ctx; 700 | napi_get_reference_value(env, e->ctx, &ctx); 701 | 702 | napi_value callback; 703 | napi_get_reference_value(env, e->on_event, &callback); 704 | 705 | if (napi_make_callback(env, NULL, ctx, callback, 0, NULL, NULL) == napi_pending_exception) { 706 | napi_value fatal_exception; 707 | napi_get_and_clear_last_exception(env, &fatal_exception); 708 | napi_fatal_exception(env, fatal_exception); 709 | } 710 | 711 | napi_close_handle_scope(env, scope); 712 | } 713 | 714 | static void 715 | on_udx_interface_event_close (udx_interface_event_t *handle) { 716 | udx_napi_interface_event_t *e = (udx_napi_interface_event_t *) handle; 717 | if (e->udx->exiting) return; 718 | 719 | napi_env env = e->env; 720 | 721 | napi_handle_scope scope; 722 | napi_open_handle_scope(env, &scope); 723 | 724 | napi_value ctx; 725 | napi_get_reference_value(env, e->ctx, &ctx); 726 | 727 | napi_value callback; 728 | napi_get_reference_value(env, e->on_close, &callback); 729 | 730 | if (napi_make_callback(env, NULL, ctx, callback, 0, NULL, NULL) == napi_pending_exception) { 731 | napi_value fatal_exception; 732 | napi_get_and_clear_last_exception(env, &fatal_exception); 733 | napi_fatal_exception(env, fatal_exception); 734 | } 735 | 736 | napi_close_handle_scope(env, scope); 737 | 738 | napi_delete_reference(env, e->on_event); 739 | napi_delete_reference(env, e->on_close); 740 | napi_delete_reference(env, e->ctx); 741 | } 742 | 743 | static void 744 | on_udx_idle (udx_t *u) { 745 | udx_napi_t *self = (udx_napi_t *) u; 746 | if (!self->has_teardown) return; 747 | 748 | self->has_teardown = false; 749 | napi_remove_async_cleanup_hook(self->teardown); 750 | } 751 | 752 | napi_value 753 | udx_napi_init (napi_env env, napi_callback_info info) { 754 | napi_value argv[2]; 755 | size_t argc = 2; 756 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 757 | 758 | udx_napi_t *self; 759 | size_t self_len; 760 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 761 | 762 | char *read_buf; 763 | size_t read_buf_len; 764 | napi_get_buffer_info(env, argv[1], (void **) &read_buf, &read_buf_len); 765 | 766 | uv_loop_t *loop; 767 | napi_get_uv_event_loop(env, &loop); 768 | 769 | udx_init(loop, &(self->udx), on_udx_idle); 770 | 771 | self->read_buf = read_buf; 772 | self->read_buf_free = read_buf_len; 773 | self->exiting = false; 774 | self->has_teardown = false; 775 | 776 | return NULL; 777 | } 778 | 779 | napi_value 780 | udx_napi_socket_init (napi_env env, napi_callback_info info) { 781 | napi_value argv[7]; 782 | size_t argc = 7; 783 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 784 | 785 | udx_napi_t *udx; 786 | size_t udx_len; 787 | napi_get_buffer_info(env, argv[0], (void **) &udx, &udx_len); 788 | 789 | udx_napi_socket_t *self; 790 | size_t self_len; 791 | napi_get_buffer_info(env, argv[1], (void **) &self, &self_len); 792 | 793 | udx_socket_t *socket = (udx_socket_t *) self; 794 | 795 | self->udx = udx; 796 | self->env = env; 797 | napi_create_reference(env, argv[2], 1, &(self->ctx)); 798 | napi_create_reference(env, argv[3], 1, &(self->on_send)); 799 | napi_create_reference(env, argv[4], 1, &(self->on_message)); 800 | napi_create_reference(env, argv[5], 1, &(self->on_close)); 801 | napi_create_reference(env, argv[6], 1, &(self->realloc_message)); 802 | 803 | int err = udx_socket_init((udx_t *) udx, socket, on_udx_close); 804 | if (err < 0) { 805 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 806 | return NULL; 807 | } 808 | 809 | ensure_teardown(env, udx); 810 | 811 | return NULL; 812 | } 813 | 814 | napi_value 815 | udx_napi_socket_bind (napi_env env, napi_callback_info info) { 816 | napi_value argv[5]; 817 | size_t argc = 5; 818 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 819 | 820 | udx_socket_t *self; 821 | size_t self_len; 822 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 823 | 824 | uint32_t port; 825 | napi_get_value_uint32(env, argv[1], &port); 826 | 827 | char ip[INET6_ADDRSTRLEN]; 828 | size_t ip_len; 829 | napi_get_value_string_utf8(env, argv[2], (char *) &ip, INET6_ADDRSTRLEN, &ip_len); 830 | 831 | uint32_t family; 832 | napi_get_value_uint32(env, argv[3], &family); 833 | 834 | uint32_t flags; 835 | napi_get_value_uint32(env, argv[4], &flags); 836 | 837 | int err; 838 | 839 | struct sockaddr_storage addr; 840 | int addr_len; 841 | 842 | if (family == 4) { 843 | addr_len = sizeof(struct sockaddr_in); 844 | err = uv_ip4_addr(ip, port, (struct sockaddr_in *) &addr); 845 | } else { 846 | addr_len = sizeof(struct sockaddr_in6); 847 | err = uv_ip6_addr(ip, port, (struct sockaddr_in6 *) &addr); 848 | } 849 | 850 | if (err < 0) { 851 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 852 | return NULL; 853 | } 854 | 855 | err = udx_socket_bind(self, (struct sockaddr *) &addr, flags); 856 | if (err < 0) { 857 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 858 | return NULL; 859 | } 860 | 861 | // TODO: move the bottom stuff into another function, start, so error handling is easier 862 | 863 | struct sockaddr_storage name; 864 | 865 | // wont error in practice 866 | err = udx_socket_getsockname(self, (struct sockaddr *) &name, &addr_len); 867 | if (err < 0) { 868 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 869 | return NULL; 870 | } 871 | 872 | int local_port; 873 | 874 | if (family == 4) { 875 | local_port = ntohs(((struct sockaddr_in *) &name)->sin_port); 876 | } else { 877 | local_port = ntohs(((struct sockaddr_in6 *) &name)->sin6_port); 878 | } 879 | 880 | // wont error in practice 881 | err = udx_socket_recv_start(self, on_udx_message); 882 | if (err < 0) { 883 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 884 | return NULL; 885 | } 886 | 887 | napi_value return_uint32; 888 | napi_create_uint32(env, local_port, &return_uint32); 889 | 890 | return return_uint32; 891 | } 892 | 893 | napi_value 894 | udx_napi_socket_set_ttl (napi_env env, napi_callback_info info) { 895 | napi_value argv[2]; 896 | size_t argc = 2; 897 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 898 | 899 | udx_socket_t *self; 900 | size_t self_len; 901 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 902 | 903 | uint32_t ttl; 904 | napi_get_value_uint32(env, argv[1], &ttl); 905 | 906 | int err = udx_socket_set_ttl(self, ttl); 907 | if (err < 0) { 908 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 909 | return NULL; 910 | } 911 | 912 | return NULL; 913 | } 914 | 915 | napi_value 916 | udx_napi_socket_set_membership (napi_env env, napi_callback_info info) { 917 | napi_value argv[4]; 918 | size_t argc = 4; 919 | 920 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 921 | 922 | udx_socket_t *socket; 923 | size_t socket_len; 924 | napi_get_buffer_info(env, argv[0], (void **) &socket, &socket_len); 925 | 926 | char mcast_addr[INET6_ADDRSTRLEN]; 927 | size_t mcast_addr_len; 928 | napi_get_value_string_utf8(env, argv[1], mcast_addr, INET6_ADDRSTRLEN, &mcast_addr_len); 929 | 930 | char iface_addr[INET6_ADDRSTRLEN]; 931 | size_t iface_addr_len; 932 | napi_get_value_string_utf8(env, argv[2], iface_addr, INET6_ADDRSTRLEN, &iface_addr_len); 933 | 934 | char *iface_param = iface_addr_len > 0 ? iface_addr : NULL; 935 | 936 | bool join; // true for join, false for leave 937 | napi_get_value_bool(env, argv[3], &join); 938 | 939 | int err = udx_socket_set_membership(socket, mcast_addr, iface_param, join ? UV_JOIN_GROUP : UV_LEAVE_GROUP); 940 | if (err < 0) { 941 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 942 | return NULL; 943 | } 944 | 945 | return NULL; 946 | } 947 | 948 | napi_value 949 | udx_napi_socket_get_recv_buffer_size (napi_env env, napi_callback_info info) { 950 | napi_value argv[1]; 951 | size_t argc = 1; 952 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 953 | 954 | udx_socket_t *self; 955 | size_t self_len; 956 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 957 | 958 | int size = 0; 959 | 960 | int err = udx_socket_get_recv_buffer_size(self, &size); 961 | if (err < 0) { 962 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 963 | return NULL; 964 | } 965 | 966 | napi_value return_uint32; 967 | napi_create_uint32(env, size, &return_uint32); 968 | 969 | return return_uint32; 970 | } 971 | 972 | napi_value 973 | udx_napi_socket_set_recv_buffer_size (napi_env env, napi_callback_info info) { 974 | napi_value argv[2]; 975 | size_t argc = 2; 976 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 977 | 978 | udx_socket_t *self; 979 | size_t self_len; 980 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 981 | 982 | int32_t size; 983 | napi_get_value_int32(env, argv[1], &size); 984 | 985 | int err = udx_socket_set_recv_buffer_size(self, size); 986 | if (err < 0) { 987 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 988 | return NULL; 989 | } 990 | 991 | return NULL; 992 | } 993 | 994 | napi_value 995 | udx_napi_socket_get_send_buffer_size (napi_env env, napi_callback_info info) { 996 | napi_value argv[1]; 997 | size_t argc = 1; 998 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 999 | 1000 | udx_socket_t *self; 1001 | size_t self_len; 1002 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 1003 | 1004 | int size = 0; 1005 | 1006 | int err = udx_socket_get_send_buffer_size(self, &size); 1007 | if (err < 0) { 1008 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1009 | return NULL; 1010 | } 1011 | 1012 | napi_value return_uint32; 1013 | napi_create_uint32(env, size, &return_uint32); 1014 | 1015 | return return_uint32; 1016 | } 1017 | 1018 | napi_value 1019 | udx_napi_socket_set_send_buffer_size (napi_env env, napi_callback_info info) { 1020 | napi_value argv[2]; 1021 | size_t argc = 2; 1022 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1023 | 1024 | udx_socket_t *self; 1025 | size_t self_len; 1026 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 1027 | 1028 | int32_t size; 1029 | napi_get_value_int32(env, argv[1], &size); 1030 | 1031 | int err = udx_socket_set_send_buffer_size(self, size); 1032 | if (err < 0) { 1033 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1034 | return NULL; 1035 | } 1036 | 1037 | napi_value return_uint32; 1038 | napi_create_uint32(env, size, &return_uint32); 1039 | 1040 | return return_uint32; 1041 | } 1042 | 1043 | napi_value 1044 | udx_napi_socket_send_ttl (napi_env env, napi_callback_info info) { 1045 | napi_value argv[8]; 1046 | size_t argc = 8; 1047 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1048 | 1049 | udx_socket_t *self; 1050 | size_t self_len; 1051 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 1052 | 1053 | udx_socket_send_t *req; 1054 | size_t req_len; 1055 | napi_get_buffer_info(env, argv[1], (void **) &req, &req_len); 1056 | 1057 | uint32_t rid; 1058 | napi_get_value_uint32(env, argv[2], &rid); 1059 | 1060 | char *buf; 1061 | size_t buf_len; 1062 | napi_get_buffer_info(env, argv[3], (void **) &buf, &buf_len); 1063 | 1064 | uint32_t port; 1065 | napi_get_value_uint32(env, argv[4], &port); 1066 | 1067 | char ip[INET6_ADDRSTRLEN]; 1068 | size_t ip_len; 1069 | napi_get_value_string_utf8(env, argv[5], (char *) &ip, INET6_ADDRSTRLEN, &ip_len); 1070 | 1071 | uint32_t family; 1072 | napi_get_value_uint32(env, argv[6], &family); 1073 | 1074 | uint32_t ttl; 1075 | napi_get_value_uint32(env, argv[7], &ttl); 1076 | 1077 | req->data = (void *) ((uintptr_t) rid); 1078 | 1079 | int err; 1080 | 1081 | struct sockaddr_storage addr; 1082 | 1083 | if (family == 4) { 1084 | err = uv_ip4_addr(ip, port, (struct sockaddr_in *) &addr); 1085 | } else { 1086 | err = uv_ip6_addr(ip, port, (struct sockaddr_in6 *) &addr); 1087 | } 1088 | 1089 | if (err < 0) { 1090 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1091 | return NULL; 1092 | } 1093 | 1094 | uv_buf_t b = uv_buf_init(buf, buf_len); 1095 | 1096 | udx_socket_send_ttl(req, self, &b, 1, (const struct sockaddr *) &addr, ttl, on_udx_send); 1097 | 1098 | if (err < 0) { 1099 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1100 | return NULL; 1101 | } 1102 | 1103 | return NULL; 1104 | } 1105 | 1106 | napi_value 1107 | udx_napi_socket_close (napi_env env, napi_callback_info info) { 1108 | napi_value argv[1]; 1109 | size_t argc = 1; 1110 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1111 | 1112 | udx_socket_t *self; 1113 | size_t self_len; 1114 | napi_get_buffer_info(env, argv[0], (void **) &self, &self_len); 1115 | 1116 | int err = udx_socket_close(self); 1117 | if (err < 0) { 1118 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1119 | return NULL; 1120 | } 1121 | 1122 | return NULL; 1123 | } 1124 | 1125 | napi_value 1126 | udx_napi_stream_init (napi_env env, napi_callback_info info) { 1127 | napi_value argv[16]; 1128 | size_t argc = 16; 1129 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1130 | 1131 | udx_napi_t *udx; 1132 | size_t udx_len; 1133 | napi_get_buffer_info(env, argv[0], (void **) &udx, &udx_len); 1134 | 1135 | udx_napi_stream_t *self; 1136 | size_t self_len; 1137 | napi_get_buffer_info(env, argv[1], (void **) &self, &self_len); 1138 | 1139 | uint32_t id; 1140 | napi_get_value_uint32(env, argv[2], &id); 1141 | 1142 | uint32_t framed; 1143 | napi_get_value_uint32(env, argv[3], &framed); 1144 | 1145 | udx_stream_t *stream = (udx_stream_t *) self; 1146 | 1147 | self->mode = framed ? UDX_NAPI_FRAMED : UDX_NAPI_INTERACTIVE; 1148 | 1149 | self->frame_len = -1; 1150 | 1151 | self->read_buf = NULL; 1152 | self->read_buf_head = NULL; 1153 | self->read_buf_free = 0; 1154 | 1155 | self->udx = udx; 1156 | self->env = env; 1157 | napi_create_reference(env, argv[4], 1, &(self->ctx)); 1158 | napi_create_reference(env, argv[5], 1, &(self->on_data)); 1159 | napi_create_reference(env, argv[6], 1, &(self->on_end)); 1160 | napi_create_reference(env, argv[7], 1, &(self->on_drain)); 1161 | napi_create_reference(env, argv[8], 1, &(self->on_ack)); 1162 | napi_create_reference(env, argv[9], 1, &(self->on_send)); 1163 | napi_create_reference(env, argv[10], 1, &(self->on_message)); 1164 | napi_create_reference(env, argv[11], 1, &(self->on_close)); 1165 | napi_create_reference(env, argv[12], 1, &(self->on_firewall)); 1166 | napi_create_reference(env, argv[13], 1, &(self->on_remote_changed)); 1167 | napi_create_reference(env, argv[14], 1, &(self->realloc_data)); 1168 | napi_create_reference(env, argv[15], 1, &(self->realloc_message)); 1169 | 1170 | int err = udx_stream_init((udx_t *) udx, stream, id, on_udx_stream_close, on_udx_stream_finalize); 1171 | if (err < 0) { 1172 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1173 | return NULL; 1174 | } 1175 | 1176 | udx_stream_firewall(stream, on_udx_stream_firewall); 1177 | udx_stream_write_resume(stream, on_udx_stream_drain); 1178 | 1179 | ensure_teardown(env, udx); 1180 | 1181 | return NULL; 1182 | } 1183 | 1184 | napi_value 1185 | udx_napi_stream_set_seq (napi_env env, napi_callback_info info) { 1186 | napi_value argv[2]; 1187 | size_t argc = 2; 1188 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1189 | 1190 | udx_stream_t *stream; 1191 | size_t stream_len; 1192 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1193 | 1194 | uint32_t seq; 1195 | napi_get_value_uint32(env, argv[1], &seq); 1196 | 1197 | int err = udx_stream_set_seq(stream, seq); 1198 | if (err < 0) { 1199 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1200 | return NULL; 1201 | } 1202 | 1203 | return NULL; 1204 | } 1205 | 1206 | napi_value 1207 | udx_napi_stream_set_ack (napi_env env, napi_callback_info info) { 1208 | napi_value argv[2]; 1209 | size_t argc = 2; 1210 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1211 | 1212 | udx_stream_t *stream; 1213 | size_t stream_len; 1214 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1215 | 1216 | uint32_t ack; 1217 | napi_get_value_uint32(env, argv[1], &ack); 1218 | 1219 | int err = udx_stream_set_ack(stream, ack); 1220 | if (err < 0) { 1221 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1222 | return NULL; 1223 | } 1224 | 1225 | return NULL; 1226 | } 1227 | 1228 | napi_value 1229 | udx_napi_stream_set_mode (napi_env env, napi_callback_info info) { 1230 | napi_value argv[2]; 1231 | size_t argc = 2; 1232 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1233 | 1234 | udx_napi_stream_t *stream; 1235 | size_t stream_len; 1236 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1237 | 1238 | uint32_t mode; 1239 | napi_get_value_uint32(env, argv[1], &mode); 1240 | 1241 | stream->mode = mode; 1242 | 1243 | return NULL; 1244 | } 1245 | 1246 | napi_value 1247 | udx_napi_stream_recv_start (napi_env env, napi_callback_info info) { 1248 | napi_value argv[2]; 1249 | size_t argc = 2; 1250 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1251 | 1252 | udx_napi_stream_t *stream; 1253 | size_t stream_len; 1254 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1255 | 1256 | char *read_buf; 1257 | size_t read_buf_len; 1258 | napi_get_buffer_info(env, argv[1], (void **) &read_buf, &read_buf_len); 1259 | 1260 | stream->read_buf = read_buf; 1261 | stream->read_buf_head = read_buf; 1262 | stream->read_buf_free = read_buf_len; 1263 | 1264 | udx_stream_read_start((udx_stream_t *) stream, on_udx_stream_read); 1265 | udx_stream_recv_start((udx_stream_t *) stream, on_udx_stream_recv); 1266 | 1267 | return NULL; 1268 | } 1269 | 1270 | napi_value 1271 | udx_napi_stream_connect (napi_env env, napi_callback_info info) { 1272 | napi_value argv[6]; 1273 | size_t argc = 6; 1274 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1275 | 1276 | udx_stream_t *stream; 1277 | size_t stream_len; 1278 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1279 | 1280 | udx_socket_t *socket; 1281 | size_t socket_len; 1282 | napi_get_buffer_info(env, argv[1], (void **) &socket, &socket_len); 1283 | 1284 | uint32_t remote_id; 1285 | napi_get_value_uint32(env, argv[2], &remote_id); 1286 | 1287 | uint32_t port; 1288 | napi_get_value_uint32(env, argv[3], &port); 1289 | 1290 | char ip[INET6_ADDRSTRLEN]; 1291 | size_t ip_len; 1292 | napi_get_value_string_utf8(env, argv[4], (char *) &ip, INET6_ADDRSTRLEN, &ip_len); 1293 | 1294 | uint32_t family; 1295 | napi_get_value_uint32(env, argv[5], &family); 1296 | 1297 | int err; 1298 | 1299 | struct sockaddr_storage addr; 1300 | 1301 | if (family == 4) { 1302 | err = uv_ip4_addr(ip, port, (struct sockaddr_in *) &addr); 1303 | } else { 1304 | err = uv_ip6_addr(ip, port, (struct sockaddr_in6 *) &addr); 1305 | } 1306 | 1307 | if (err < 0) { 1308 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1309 | return NULL; 1310 | } 1311 | 1312 | err = udx_stream_connect(stream, socket, remote_id, (const struct sockaddr *) &addr); 1313 | 1314 | if (err < 0) { 1315 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1316 | return NULL; 1317 | } 1318 | 1319 | return NULL; 1320 | } 1321 | 1322 | napi_value 1323 | udx_napi_stream_change_remote (napi_env env, napi_callback_info info) { 1324 | napi_value argv[6]; 1325 | size_t argc = 6; 1326 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1327 | 1328 | udx_stream_t *stream; 1329 | size_t stream_len; 1330 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1331 | 1332 | udx_socket_t *socket; 1333 | size_t socket_len; 1334 | napi_get_buffer_info(env, argv[1], (void **) &socket, &socket_len); 1335 | 1336 | uint32_t remote_id; 1337 | napi_get_value_uint32(env, argv[2], &remote_id); 1338 | 1339 | uint32_t port; 1340 | napi_get_value_uint32(env, argv[3], &port); 1341 | 1342 | char ip[INET6_ADDRSTRLEN]; 1343 | size_t ip_len; 1344 | napi_get_value_string_utf8(env, argv[4], (char *) &ip, INET6_ADDRSTRLEN, &ip_len); 1345 | 1346 | uint32_t family; 1347 | napi_get_value_uint32(env, argv[5], &family); 1348 | 1349 | int err; 1350 | 1351 | struct sockaddr_storage addr; 1352 | 1353 | if (family == 4) { 1354 | err = uv_ip4_addr(ip, port, (struct sockaddr_in *) &addr); 1355 | } else { 1356 | err = uv_ip6_addr(ip, port, (struct sockaddr_in6 *) &addr); 1357 | } 1358 | 1359 | if (err < 0) { 1360 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1361 | return NULL; 1362 | } 1363 | 1364 | err = udx_stream_change_remote(stream, socket, remote_id, (const struct sockaddr *) &addr, on_udx_stream_remote_changed); 1365 | 1366 | if (err < 0) { 1367 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1368 | return NULL; 1369 | } 1370 | 1371 | return NULL; 1372 | } 1373 | 1374 | napi_value 1375 | udx_napi_stream_relay_to (napi_env env, napi_callback_info info) { 1376 | napi_value argv[2]; 1377 | size_t argc = 2; 1378 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1379 | 1380 | udx_stream_t *stream; 1381 | size_t stream_len; 1382 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1383 | 1384 | udx_stream_t *destination; 1385 | size_t destination_len; 1386 | napi_get_buffer_info(env, argv[1], (void **) &destination, &destination_len); 1387 | 1388 | int err = udx_stream_relay_to(stream, destination); 1389 | if (err < 0) { 1390 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1391 | return NULL; 1392 | } 1393 | 1394 | return NULL; 1395 | } 1396 | 1397 | napi_value 1398 | udx_napi_stream_send (napi_env env, napi_callback_info info) { 1399 | napi_value argv[4]; 1400 | size_t argc = 4; 1401 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1402 | 1403 | udx_stream_t *stream; 1404 | size_t stream_len; 1405 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1406 | 1407 | udx_stream_send_t *req; 1408 | size_t req_len; 1409 | napi_get_buffer_info(env, argv[1], (void **) &req, &req_len); 1410 | 1411 | uint32_t rid; 1412 | napi_get_value_uint32(env, argv[2], &rid); 1413 | 1414 | char *buf; 1415 | size_t buf_len; 1416 | napi_get_buffer_info(env, argv[3], (void **) &buf, &buf_len); 1417 | 1418 | req->data = (void *) ((uintptr_t) rid); 1419 | 1420 | uv_buf_t b = uv_buf_init(buf, buf_len); 1421 | 1422 | int err = udx_stream_send(req, stream, &b, 1, on_udx_stream_send); 1423 | if (err < 0) { 1424 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1425 | return NULL; 1426 | } 1427 | 1428 | napi_value return_uint32; 1429 | napi_create_uint32(env, err, &return_uint32); 1430 | 1431 | return return_uint32; 1432 | } 1433 | 1434 | napi_value 1435 | udx_napi_stream_write (napi_env env, napi_callback_info info) { 1436 | napi_value argv[4]; 1437 | size_t argc = 4; 1438 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1439 | 1440 | udx_stream_t *stream; 1441 | size_t stream_len; 1442 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1443 | 1444 | udx_stream_write_t *req; 1445 | size_t req_len; 1446 | napi_get_buffer_info(env, argv[1], (void **) &req, &req_len); 1447 | 1448 | uint32_t rid; 1449 | napi_get_value_uint32(env, argv[2], &rid); 1450 | 1451 | char *buf; 1452 | size_t buf_len; 1453 | napi_get_buffer_info(env, argv[3], (void **) &buf, &buf_len); 1454 | 1455 | req->data = (void *) ((uintptr_t) rid); 1456 | 1457 | uv_buf_t b = uv_buf_init(buf, buf_len); 1458 | 1459 | int err = udx_stream_write(req, stream, &b, 1, on_udx_stream_ack); 1460 | if (err < 0) { 1461 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1462 | return NULL; 1463 | } 1464 | 1465 | napi_value return_uint32; 1466 | napi_create_uint32(env, err, &return_uint32); 1467 | 1468 | return return_uint32; 1469 | } 1470 | 1471 | napi_value 1472 | udx_napi_stream_writev (napi_env env, napi_callback_info info) { 1473 | napi_value argv[4]; 1474 | size_t argc = 4; 1475 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1476 | 1477 | udx_stream_t *stream; 1478 | size_t stream_len; 1479 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1480 | 1481 | udx_stream_write_t *req; 1482 | size_t req_len; 1483 | napi_get_buffer_info(env, argv[1], (void **) &req, &req_len); 1484 | 1485 | uint32_t rid; 1486 | napi_get_value_uint32(env, argv[2], &rid); 1487 | 1488 | napi_value buffers = argv[3]; 1489 | 1490 | req->data = (void *) ((uintptr_t) rid); 1491 | 1492 | uint32_t len; 1493 | napi_get_array_length(env, buffers, &len); 1494 | uv_buf_t *batch = malloc(sizeof(uv_buf_t) * len); 1495 | 1496 | napi_value element; 1497 | for (uint32_t i = 0; i < len; i++) { 1498 | napi_get_element(env, buffers, i, &element); 1499 | 1500 | char *buf; 1501 | size_t buf_len; 1502 | napi_get_buffer_info(env, element, (void **) &buf, &buf_len); 1503 | 1504 | batch[i] = uv_buf_init(buf, buf_len); 1505 | } 1506 | 1507 | int err = udx_stream_write(req, stream, batch, len, on_udx_stream_ack); 1508 | free(batch); 1509 | 1510 | if (err < 0) { 1511 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1512 | return NULL; 1513 | } 1514 | 1515 | napi_value return_uint32; 1516 | napi_create_uint32(env, err, &return_uint32); 1517 | 1518 | return return_uint32; 1519 | } 1520 | 1521 | napi_value 1522 | udx_napi_stream_write_sizeof (napi_env env, napi_callback_info info) { 1523 | napi_value argv[1]; 1524 | size_t argc = 1; 1525 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1526 | 1527 | uint32_t bufs; 1528 | napi_get_value_uint32(env, argv[0], &bufs); 1529 | 1530 | napi_value return_uint32; 1531 | napi_create_uint32(env, udx_stream_write_sizeof(bufs), &return_uint32); 1532 | 1533 | return return_uint32; 1534 | } 1535 | 1536 | napi_value 1537 | udx_napi_stream_write_end (napi_env env, napi_callback_info info) { 1538 | napi_value argv[4]; 1539 | size_t argc = 4; 1540 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1541 | 1542 | udx_stream_t *stream; 1543 | size_t stream_len; 1544 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1545 | 1546 | udx_stream_write_t *req; 1547 | size_t req_len; 1548 | napi_get_buffer_info(env, argv[1], (void **) &req, &req_len); 1549 | 1550 | uint32_t rid; 1551 | napi_get_value_uint32(env, argv[2], &rid); 1552 | 1553 | char *buf; 1554 | size_t buf_len; 1555 | napi_get_buffer_info(env, argv[3], (void **) &buf, &buf_len); 1556 | 1557 | req->data = (void *) ((uintptr_t) rid); 1558 | 1559 | uv_buf_t b = uv_buf_init(buf, buf_len); 1560 | 1561 | int err = udx_stream_write_end(req, stream, &b, 1, on_udx_stream_ack); 1562 | if (err < 0) { 1563 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1564 | return NULL; 1565 | } 1566 | 1567 | napi_value return_uint32; 1568 | napi_create_uint32(env, err, &return_uint32); 1569 | 1570 | return return_uint32; 1571 | } 1572 | 1573 | napi_value 1574 | udx_napi_stream_destroy (napi_env env, napi_callback_info info) { 1575 | napi_value argv[1]; 1576 | size_t argc = 1; 1577 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1578 | 1579 | udx_stream_t *stream; 1580 | size_t stream_len; 1581 | napi_get_buffer_info(env, argv[0], (void **) &stream, &stream_len); 1582 | 1583 | int err = udx_stream_destroy(stream); 1584 | if (err < 0) { 1585 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1586 | return NULL; 1587 | } 1588 | 1589 | napi_value return_uint32; 1590 | napi_create_uint32(env, err, &return_uint32); 1591 | 1592 | return return_uint32; 1593 | } 1594 | 1595 | napi_value 1596 | udx_napi_lookup (napi_env env, napi_callback_info info) { 1597 | napi_value argv[6]; 1598 | size_t argc = 6; 1599 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1600 | 1601 | udx_napi_t *udx; 1602 | size_t udx_len; 1603 | napi_get_buffer_info(env, argv[0], (void **) &udx, &udx_len); 1604 | 1605 | udx_napi_lookup_t *self; 1606 | size_t self_len; 1607 | napi_get_buffer_info(env, argv[1], (void **) &self, &self_len); 1608 | 1609 | self->udx = udx; 1610 | 1611 | size_t host_size = 0; 1612 | napi_get_value_string_utf8(env, argv[2], NULL, 0, &host_size); 1613 | 1614 | char *host = (char *) malloc((host_size + 1) * sizeof(char)); 1615 | size_t host_len; 1616 | napi_get_value_string_utf8(env, argv[2], host, host_size + 1, &host_len); 1617 | host[host_size] = '\0'; 1618 | 1619 | uint32_t family; 1620 | napi_get_value_uint32(env, argv[3], &family); 1621 | 1622 | udx_lookup_t *lookup = (udx_lookup_t *) self; 1623 | 1624 | self->host = host; 1625 | self->env = env; 1626 | napi_create_reference(env, argv[4], 1, &(self->ctx)); 1627 | napi_create_reference(env, argv[5], 1, &(self->on_lookup)); 1628 | 1629 | int flags = 0; 1630 | 1631 | if (family == 4) flags |= UDX_LOOKUP_FAMILY_IPV4; 1632 | if (family == 6) flags |= UDX_LOOKUP_FAMILY_IPV6; 1633 | 1634 | int err = udx_lookup((udx_t *) udx, lookup, host, flags, on_udx_lookup); 1635 | if (err < 0) { 1636 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1637 | return NULL; 1638 | } 1639 | 1640 | ensure_teardown(env, udx); 1641 | 1642 | return NULL; 1643 | } 1644 | 1645 | napi_value 1646 | udx_napi_interface_event_init (napi_env env, napi_callback_info info) { 1647 | napi_value argv[5]; 1648 | size_t argc = 5; 1649 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1650 | 1651 | udx_napi_t *udx; 1652 | size_t udx_len; 1653 | napi_get_buffer_info(env, argv[0], (void **) &udx, &udx_len); 1654 | 1655 | udx_napi_interface_event_t *self; 1656 | size_t self_len; 1657 | napi_get_buffer_info(env, argv[1], (void **) &self, &self_len); 1658 | 1659 | self->udx = udx; 1660 | 1661 | udx_interface_event_t *event = (udx_interface_event_t *) self; 1662 | 1663 | self->env = env; 1664 | napi_create_reference(env, argv[2], 1, &(self->ctx)); 1665 | napi_create_reference(env, argv[3], 1, &(self->on_event)); 1666 | napi_create_reference(env, argv[4], 1, &(self->on_close)); 1667 | 1668 | int err = udx_interface_event_init((udx_t *) udx, event, on_udx_interface_event_close); 1669 | if (err < 0) { 1670 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1671 | return NULL; 1672 | } 1673 | 1674 | err = udx_interface_event_start(event, on_udx_interface_event, 5000); 1675 | if (err < 0) { 1676 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1677 | return NULL; 1678 | } 1679 | 1680 | ensure_teardown(env, udx); 1681 | 1682 | return NULL; 1683 | } 1684 | 1685 | napi_value 1686 | udx_napi_interface_event_start (napi_env env, napi_callback_info info) { 1687 | napi_value argv[1]; 1688 | size_t argc = 1; 1689 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1690 | 1691 | udx_interface_event_t *event; 1692 | size_t event_len; 1693 | napi_get_buffer_info(env, argv[0], (void **) &event, &event_len); 1694 | 1695 | int err = udx_interface_event_start(event, on_udx_interface_event, 5000); 1696 | if (err < 0) { 1697 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1698 | return NULL; 1699 | } 1700 | 1701 | return NULL; 1702 | } 1703 | 1704 | napi_value 1705 | udx_napi_interface_event_stop (napi_env env, napi_callback_info info) { 1706 | napi_value argv[1]; 1707 | size_t argc = 1; 1708 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1709 | 1710 | udx_interface_event_t *event; 1711 | size_t event_len; 1712 | napi_get_buffer_info(env, argv[0], (void **) &event, &event_len); 1713 | 1714 | int err = udx_interface_event_stop(event); 1715 | if (err < 0) { 1716 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1717 | return NULL; 1718 | } 1719 | 1720 | return NULL; 1721 | } 1722 | 1723 | napi_value 1724 | udx_napi_interface_event_close (napi_env env, napi_callback_info info) { 1725 | napi_value argv[1]; 1726 | size_t argc = 1; 1727 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1728 | 1729 | udx_interface_event_t *event; 1730 | size_t event_len; 1731 | napi_get_buffer_info(env, argv[0], (void **) &event, &event_len); 1732 | 1733 | int err = udx_interface_event_close(event); 1734 | if (err < 0) { 1735 | napi_throw_error(env, uv_err_name(err), uv_strerror(err)); 1736 | return NULL; 1737 | } 1738 | 1739 | return NULL; 1740 | } 1741 | 1742 | napi_value 1743 | udx_napi_interface_event_get_addrs (napi_env env, napi_callback_info info) { 1744 | napi_value argv[1]; 1745 | size_t argc = 1; 1746 | napi_get_cb_info(env, info, &argc, argv, NULL, NULL); 1747 | 1748 | udx_interface_event_t *event; 1749 | size_t event_len; 1750 | napi_get_buffer_info(env, argv[0], (void **) &event, &event_len); 1751 | 1752 | char ip[INET6_ADDRSTRLEN]; 1753 | int family = 0; 1754 | 1755 | napi_value napi_result; 1756 | napi_create_array(env, &napi_result); 1757 | 1758 | for (int i = 0, j = 0; i < event->addrs_len; i++) { 1759 | uv_interface_address_t addr = event->addrs[i]; 1760 | 1761 | if (addr.address.address4.sin_family == AF_INET) { 1762 | uv_ip4_name(&addr.address.address4, ip, sizeof(ip)); 1763 | family = 4; 1764 | } else if (addr.address.address4.sin_family == AF_INET6) { 1765 | uv_ip6_name(&addr.address.address6, ip, sizeof(ip)); 1766 | family = 6; 1767 | } else { 1768 | continue; 1769 | } 1770 | 1771 | napi_value napi_item; 1772 | napi_create_object(env, &napi_item); 1773 | napi_set_element(env, napi_result, j++, napi_item); 1774 | 1775 | napi_value napi_name; 1776 | napi_create_string_utf8(env, addr.name, NAPI_AUTO_LENGTH, &napi_name); 1777 | napi_set_named_property(env, napi_item, "name", napi_name); 1778 | 1779 | napi_value napi_ip; 1780 | napi_create_string_utf8(env, ip, NAPI_AUTO_LENGTH, &napi_ip); 1781 | napi_set_named_property(env, napi_item, "host", napi_ip); 1782 | 1783 | napi_value napi_family; 1784 | napi_create_uint32(env, family, &napi_family); 1785 | napi_set_named_property(env, napi_item, "family", napi_family); 1786 | 1787 | napi_value napi_internal; 1788 | napi_get_boolean(env, addr.is_internal, &napi_internal); 1789 | napi_set_named_property(env, napi_item, "internal", napi_internal); 1790 | } 1791 | 1792 | return napi_result; 1793 | } 1794 | 1795 | static void 1796 | napi_macros_init (napi_env env, napi_value exports); 1797 | 1798 | static napi_value 1799 | napi_macros_init_wrap (napi_env env, napi_value exports) { 1800 | napi_macros_init(env, exports); 1801 | return exports; 1802 | } 1803 | 1804 | NAPI_MODULE(NODE_GYP_MODULE_NAME, napi_macros_init_wrap) 1805 | static void 1806 | napi_macros_init (napi_env env, napi_value exports) { 1807 | 1808 | napi_value UV_UDP_IPV6ONLY_uint32; 1809 | napi_create_uint32(env, UV_UDP_IPV6ONLY, &UV_UDP_IPV6ONLY_uint32); 1810 | napi_set_named_property(env, exports, "UV_UDP_IPV6ONLY", UV_UDP_IPV6ONLY_uint32); 1811 | 1812 | napi_value UV_UDP_REUSEADDR_uint32; 1813 | napi_create_uint32(env, UV_UDP_REUSEADDR, &UV_UDP_REUSEADDR_uint32); 1814 | napi_set_named_property(env, exports, "UV_UDP_REUSEADDR", UV_UDP_REUSEADDR_uint32); 1815 | 1816 | napi_value inflight_offsetof; 1817 | napi_create_uint32(env, offsetof(udx_stream_t, inflight), &inflight_offsetof); 1818 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_inflight", inflight_offsetof); 1819 | 1820 | napi_value mtu_offsetof; 1821 | napi_create_uint32(env, offsetof(udx_stream_t, mtu), &mtu_offsetof); 1822 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_mtu", mtu_offsetof); 1823 | 1824 | napi_value cwnd_offsetof; 1825 | napi_create_uint32(env, offsetof(udx_stream_t, cwnd), &cwnd_offsetof); 1826 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_cwnd", cwnd_offsetof); 1827 | 1828 | napi_value srtt_offsetof; 1829 | napi_create_uint32(env, offsetof(udx_stream_t, srtt), &srtt_offsetof); 1830 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_srtt", srtt_offsetof); 1831 | 1832 | napi_value bytes_rx_offsetof; 1833 | napi_create_uint32(env, offsetof(udx_stream_t, bytes_rx), &bytes_rx_offsetof); 1834 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_bytes_rx", bytes_rx_offsetof); 1835 | 1836 | napi_value packets_rx_offsetof; 1837 | napi_create_uint32(env, offsetof(udx_stream_t, packets_rx), &packets_rx_offsetof); 1838 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_packets_rx", packets_rx_offsetof); 1839 | 1840 | napi_value bytes_tx_offsetof; 1841 | napi_create_uint32(env, offsetof(udx_stream_t, bytes_tx), &bytes_tx_offsetof); 1842 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_bytes_tx", bytes_tx_offsetof); 1843 | 1844 | napi_value packets_tx_offsetof; 1845 | napi_create_uint32(env, offsetof(udx_stream_t, packets_tx), &packets_tx_offsetof); 1846 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_packets_tx", packets_tx_offsetof); 1847 | 1848 | napi_value rto_count_offsetof; 1849 | napi_create_uint32(env, offsetof(udx_stream_t, rto_count), &rto_count_offsetof); 1850 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_rto_count", rto_count_offsetof); 1851 | 1852 | napi_value retransmit_count_offsetof; 1853 | napi_create_uint32(env, offsetof(udx_stream_t, retransmit_count), &retransmit_count_offsetof); 1854 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_retransmit_count", retransmit_count_offsetof); 1855 | 1856 | napi_value fast_recovery_count_offsetof; 1857 | napi_create_uint32(env, offsetof(udx_stream_t, fast_recovery_count), &fast_recovery_count_offsetof); 1858 | napi_set_named_property(env, exports, "offsetof_udx_stream_t_fast_recovery_count", fast_recovery_count_offsetof); 1859 | 1860 | napi_value socket_bytes_rx_offsetof; 1861 | napi_create_uint32(env, offsetof(udx_socket_t, bytes_rx), &socket_bytes_rx_offsetof); 1862 | napi_set_named_property(env, exports, "offsetof_udx_socket_t_bytes_rx", socket_bytes_rx_offsetof); 1863 | 1864 | napi_value socket_packets_rx_offsetof; 1865 | napi_create_uint32(env, offsetof(udx_socket_t, packets_rx), &socket_packets_rx_offsetof); 1866 | napi_set_named_property(env, exports, "offsetof_udx_socket_t_packets_rx", socket_packets_rx_offsetof); 1867 | 1868 | napi_value socket_bytes_tx_offsetof; 1869 | napi_create_uint32(env, offsetof(udx_socket_t, bytes_tx), &socket_bytes_tx_offsetof); 1870 | napi_set_named_property(env, exports, "offsetof_udx_socket_t_bytes_tx", socket_bytes_tx_offsetof); 1871 | 1872 | napi_value socket_packets_tx_offsetof; 1873 | napi_create_uint32(env, offsetof(udx_socket_t, packets_tx), &socket_packets_tx_offsetof); 1874 | napi_set_named_property(env, exports, "offsetof_udx_socket_t_packets_tx", socket_packets_tx_offsetof); 1875 | 1876 | napi_value packets_dropped_by_kernel_offsetof; 1877 | napi_create_uint32(env, offsetof(udx_socket_t, packets_dropped_by_kernel), &packets_dropped_by_kernel_offsetof); 1878 | napi_set_named_property(env, exports, "offsetof_udx_socket_t_packets_dropped_by_kernel", packets_dropped_by_kernel_offsetof); 1879 | 1880 | napi_value udx_bytes_rx_offsetof; 1881 | napi_create_uint32(env, offsetof(udx_t, bytes_rx), &udx_bytes_rx_offsetof); 1882 | napi_set_named_property(env, exports, "offsetof_udx_t_bytes_rx", udx_bytes_rx_offsetof); 1883 | 1884 | napi_value udx_packets_rx_offsetof; 1885 | napi_create_uint32(env, offsetof(udx_t, packets_rx), &udx_packets_rx_offsetof); 1886 | napi_set_named_property(env, exports, "offsetof_udx_t_packets_rx", udx_packets_rx_offsetof); 1887 | 1888 | napi_value udx_bytes_tx_offsetof; 1889 | napi_create_uint32(env, offsetof(udx_t, bytes_tx), &udx_bytes_tx_offsetof); 1890 | napi_set_named_property(env, exports, "offsetof_udx_t_bytes_tx", udx_bytes_tx_offsetof); 1891 | 1892 | napi_value udx_packets_tx_offsetof; 1893 | napi_create_uint32(env, offsetof(udx_t, packets_tx), &udx_packets_tx_offsetof); 1894 | napi_set_named_property(env, exports, "offsetof_udx_t_packets_tx", udx_packets_tx_offsetof); 1895 | 1896 | napi_value udx_packets_dropped_by_kernel_offsetof; 1897 | napi_create_uint32(env, offsetof(udx_t, packets_dropped_by_kernel), &udx_packets_dropped_by_kernel_offsetof); 1898 | napi_set_named_property(env, exports, "offsetof_udx_t_packets_dropped_by_kernel", udx_packets_dropped_by_kernel_offsetof); 1899 | 1900 | napi_value udx_napi_t_sizeof; 1901 | napi_create_uint32(env, sizeof(udx_napi_t), &udx_napi_t_sizeof); 1902 | napi_set_named_property(env, exports, "sizeof_udx_napi_t", udx_napi_t_sizeof); 1903 | 1904 | napi_value udx_napi_socket_t_sizeof; 1905 | napi_create_uint32(env, sizeof(udx_napi_socket_t), &udx_napi_socket_t_sizeof); 1906 | napi_set_named_property(env, exports, "sizeof_udx_napi_socket_t", udx_napi_socket_t_sizeof); 1907 | 1908 | napi_value udx_napi_stream_t_sizeof; 1909 | napi_create_uint32(env, sizeof(udx_napi_stream_t), &udx_napi_stream_t_sizeof); 1910 | napi_set_named_property(env, exports, "sizeof_udx_napi_stream_t", udx_napi_stream_t_sizeof); 1911 | 1912 | napi_value udx_napi_lookup_t_sizeof; 1913 | napi_create_uint32(env, sizeof(udx_napi_lookup_t), &udx_napi_lookup_t_sizeof); 1914 | napi_set_named_property(env, exports, "sizeof_udx_napi_lookup_t", udx_napi_lookup_t_sizeof); 1915 | 1916 | napi_value udx_napi_interface_event_t_sizeof; 1917 | napi_create_uint32(env, sizeof(udx_napi_interface_event_t), &udx_napi_interface_event_t_sizeof); 1918 | napi_set_named_property(env, exports, "sizeof_udx_napi_interface_event_t", udx_napi_interface_event_t_sizeof); 1919 | 1920 | napi_value udx_socket_send_t_sizeof; 1921 | napi_create_uint32(env, sizeof(udx_socket_send_t), &udx_socket_send_t_sizeof); 1922 | napi_set_named_property(env, exports, "sizeof_udx_socket_send_t", udx_socket_send_t_sizeof); 1923 | 1924 | napi_value udx_stream_send_t_sizeof; 1925 | napi_create_uint32(env, sizeof(udx_stream_send_t), &udx_stream_send_t_sizeof); 1926 | napi_set_named_property(env, exports, "sizeof_udx_stream_send_t", udx_stream_send_t_sizeof); 1927 | 1928 | napi_value udx_napi_init_fn; 1929 | napi_create_function(env, NULL, 0, udx_napi_init, NULL, &udx_napi_init_fn); 1930 | napi_set_named_property(env, exports, "udx_napi_init", udx_napi_init_fn); 1931 | 1932 | napi_value udx_napi_socket_init_fn; 1933 | napi_create_function(env, NULL, 0, udx_napi_socket_init, NULL, &udx_napi_socket_init_fn); 1934 | napi_set_named_property(env, exports, "udx_napi_socket_init", udx_napi_socket_init_fn); 1935 | 1936 | napi_value udx_napi_socket_bind_fn; 1937 | napi_create_function(env, NULL, 0, udx_napi_socket_bind, NULL, &udx_napi_socket_bind_fn); 1938 | napi_set_named_property(env, exports, "udx_napi_socket_bind", udx_napi_socket_bind_fn); 1939 | 1940 | napi_value udx_napi_socket_set_ttl_fn; 1941 | napi_create_function(env, NULL, 0, udx_napi_socket_set_ttl, NULL, &udx_napi_socket_set_ttl_fn); 1942 | napi_set_named_property(env, exports, "udx_napi_socket_set_ttl", udx_napi_socket_set_ttl_fn); 1943 | 1944 | napi_value udx_napi_socket_get_recv_buffer_size_fn; 1945 | napi_create_function(env, NULL, 0, udx_napi_socket_get_recv_buffer_size, NULL, &udx_napi_socket_get_recv_buffer_size_fn); 1946 | napi_set_named_property(env, exports, "udx_napi_socket_get_recv_buffer_size", udx_napi_socket_get_recv_buffer_size_fn); 1947 | 1948 | napi_value udx_napi_socket_set_recv_buffer_size_fn; 1949 | napi_create_function(env, NULL, 0, udx_napi_socket_set_recv_buffer_size, NULL, &udx_napi_socket_set_recv_buffer_size_fn); 1950 | napi_set_named_property(env, exports, "udx_napi_socket_set_recv_buffer_size", udx_napi_socket_set_recv_buffer_size_fn); 1951 | 1952 | napi_value udx_napi_socket_get_send_buffer_size_fn; 1953 | napi_create_function(env, NULL, 0, udx_napi_socket_get_send_buffer_size, NULL, &udx_napi_socket_get_send_buffer_size_fn); 1954 | napi_set_named_property(env, exports, "udx_napi_socket_get_send_buffer_size", udx_napi_socket_get_send_buffer_size_fn); 1955 | 1956 | napi_value udx_napi_socket_set_send_buffer_size_fn; 1957 | napi_create_function(env, NULL, 0, udx_napi_socket_set_send_buffer_size, NULL, &udx_napi_socket_set_send_buffer_size_fn); 1958 | napi_set_named_property(env, exports, "udx_napi_socket_set_send_buffer_size", udx_napi_socket_set_send_buffer_size_fn); 1959 | 1960 | napi_value udx_napi_socket_set_membership_fn; 1961 | napi_create_function(env, NULL, 0, udx_napi_socket_set_membership, NULL, &udx_napi_socket_set_membership_fn); 1962 | napi_set_named_property(env, exports, "udx_napi_socket_set_membership", udx_napi_socket_set_membership_fn); 1963 | 1964 | napi_value udx_napi_socket_send_ttl_fn; 1965 | napi_create_function(env, NULL, 0, udx_napi_socket_send_ttl, NULL, &udx_napi_socket_send_ttl_fn); 1966 | napi_set_named_property(env, exports, "udx_napi_socket_send_ttl", udx_napi_socket_send_ttl_fn); 1967 | 1968 | napi_value udx_napi_socket_close_fn; 1969 | napi_create_function(env, NULL, 0, udx_napi_socket_close, NULL, &udx_napi_socket_close_fn); 1970 | napi_set_named_property(env, exports, "udx_napi_socket_close", udx_napi_socket_close_fn); 1971 | 1972 | napi_value udx_napi_stream_init_fn; 1973 | napi_create_function(env, NULL, 0, udx_napi_stream_init, NULL, &udx_napi_stream_init_fn); 1974 | napi_set_named_property(env, exports, "udx_napi_stream_init", udx_napi_stream_init_fn); 1975 | 1976 | napi_value udx_napi_stream_set_seq_fn; 1977 | napi_create_function(env, NULL, 0, udx_napi_stream_set_seq, NULL, &udx_napi_stream_set_seq_fn); 1978 | napi_set_named_property(env, exports, "udx_napi_stream_set_seq", udx_napi_stream_set_seq_fn); 1979 | 1980 | napi_value udx_napi_stream_set_ack_fn; 1981 | napi_create_function(env, NULL, 0, udx_napi_stream_set_ack, NULL, &udx_napi_stream_set_ack_fn); 1982 | napi_set_named_property(env, exports, "udx_napi_stream_set_ack", udx_napi_stream_set_ack_fn); 1983 | 1984 | napi_value udx_napi_stream_set_mode_fn; 1985 | napi_create_function(env, NULL, 0, udx_napi_stream_set_mode, NULL, &udx_napi_stream_set_mode_fn); 1986 | napi_set_named_property(env, exports, "udx_napi_stream_set_mode", udx_napi_stream_set_mode_fn); 1987 | 1988 | napi_value udx_napi_stream_connect_fn; 1989 | napi_create_function(env, NULL, 0, udx_napi_stream_connect, NULL, &udx_napi_stream_connect_fn); 1990 | napi_set_named_property(env, exports, "udx_napi_stream_connect", udx_napi_stream_connect_fn); 1991 | 1992 | napi_value udx_napi_stream_change_remote_fn; 1993 | napi_create_function(env, NULL, 0, udx_napi_stream_change_remote, NULL, &udx_napi_stream_change_remote_fn); 1994 | napi_set_named_property(env, exports, "udx_napi_stream_change_remote", udx_napi_stream_change_remote_fn); 1995 | 1996 | napi_value udx_napi_stream_relay_to_fn; 1997 | napi_create_function(env, NULL, 0, udx_napi_stream_relay_to, NULL, &udx_napi_stream_relay_to_fn); 1998 | napi_set_named_property(env, exports, "udx_napi_stream_relay_to", udx_napi_stream_relay_to_fn); 1999 | 2000 | napi_value udx_napi_stream_send_fn; 2001 | napi_create_function(env, NULL, 0, udx_napi_stream_send, NULL, &udx_napi_stream_send_fn); 2002 | napi_set_named_property(env, exports, "udx_napi_stream_send", udx_napi_stream_send_fn); 2003 | 2004 | napi_value udx_napi_stream_recv_start_fn; 2005 | napi_create_function(env, NULL, 0, udx_napi_stream_recv_start, NULL, &udx_napi_stream_recv_start_fn); 2006 | napi_set_named_property(env, exports, "udx_napi_stream_recv_start", udx_napi_stream_recv_start_fn); 2007 | 2008 | napi_value udx_napi_stream_write_fn; 2009 | napi_create_function(env, NULL, 0, udx_napi_stream_write, NULL, &udx_napi_stream_write_fn); 2010 | napi_set_named_property(env, exports, "udx_napi_stream_write", udx_napi_stream_write_fn); 2011 | 2012 | napi_value udx_napi_stream_writev_fn; 2013 | napi_create_function(env, NULL, 0, udx_napi_stream_writev, NULL, &udx_napi_stream_writev_fn); 2014 | napi_set_named_property(env, exports, "udx_napi_stream_writev", udx_napi_stream_writev_fn); 2015 | 2016 | napi_value udx_napi_stream_write_sizeof_fn; 2017 | napi_create_function(env, NULL, 0, udx_napi_stream_write_sizeof, NULL, &udx_napi_stream_write_sizeof_fn); 2018 | napi_set_named_property(env, exports, "udx_napi_stream_write_sizeof", udx_napi_stream_write_sizeof_fn); 2019 | 2020 | napi_value udx_napi_stream_write_end_fn; 2021 | napi_create_function(env, NULL, 0, udx_napi_stream_write_end, NULL, &udx_napi_stream_write_end_fn); 2022 | napi_set_named_property(env, exports, "udx_napi_stream_write_end", udx_napi_stream_write_end_fn); 2023 | 2024 | napi_value udx_napi_stream_destroy_fn; 2025 | napi_create_function(env, NULL, 0, udx_napi_stream_destroy, NULL, &udx_napi_stream_destroy_fn); 2026 | napi_set_named_property(env, exports, "udx_napi_stream_destroy", udx_napi_stream_destroy_fn); 2027 | 2028 | napi_value udx_napi_lookup_fn; 2029 | napi_create_function(env, NULL, 0, udx_napi_lookup, NULL, &udx_napi_lookup_fn); 2030 | napi_set_named_property(env, exports, "udx_napi_lookup", udx_napi_lookup_fn); 2031 | 2032 | napi_value udx_napi_interface_event_init_fn; 2033 | napi_create_function(env, NULL, 0, udx_napi_interface_event_init, NULL, &udx_napi_interface_event_init_fn); 2034 | napi_set_named_property(env, exports, "udx_napi_interface_event_init", udx_napi_interface_event_init_fn); 2035 | 2036 | napi_value udx_napi_interface_event_start_fn; 2037 | napi_create_function(env, NULL, 0, udx_napi_interface_event_start, NULL, &udx_napi_interface_event_start_fn); 2038 | napi_set_named_property(env, exports, "udx_napi_interface_event_start", udx_napi_interface_event_start_fn); 2039 | 2040 | napi_value udx_napi_interface_event_stop_fn; 2041 | napi_create_function(env, NULL, 0, udx_napi_interface_event_stop, NULL, &udx_napi_interface_event_stop_fn); 2042 | napi_set_named_property(env, exports, "udx_napi_interface_event_stop", udx_napi_interface_event_stop_fn); 2043 | 2044 | napi_value udx_napi_interface_event_close_fn; 2045 | napi_create_function(env, NULL, 0, udx_napi_interface_event_close, NULL, &udx_napi_interface_event_close_fn); 2046 | napi_set_named_property(env, exports, "udx_napi_interface_event_close", udx_napi_interface_event_close_fn); 2047 | 2048 | napi_value udx_napi_interface_event_get_addrs_fn; 2049 | napi_create_function(env, NULL, 0, udx_napi_interface_event_get_addrs, NULL, &udx_napi_interface_event_get_addrs_fn); 2050 | napi_set_named_property(env, exports, "udx_napi_interface_event_get_addrs", udx_napi_interface_event_get_addrs_fn); 2051 | } 2052 | -------------------------------------------------------------------------------- /binding.js: -------------------------------------------------------------------------------- 1 | require.addon = require('require-addon') 2 | 3 | module.exports = require.addon('.', __filename) 4 | -------------------------------------------------------------------------------- /lib/ip.js: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | const v4Seg = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])' 23 | const v4Str = `(${v4Seg}[.]){3}${v4Seg}` 24 | const IPv4Pattern = new RegExp(`^${v4Str}$`) 25 | 26 | const v6Seg = '(?:[0-9a-fA-F]{1,4})' 27 | const IPv6Pattern = new RegExp('^(' + 28 | `(?:${v6Seg}:){7}(?:${v6Seg}|:)|` + 29 | `(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|` + 30 | `(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|` + 31 | `(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|` + 32 | `(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|` + 33 | `(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|` + 34 | `(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|` + 35 | `(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:))` + 36 | ')(%[0-9a-zA-Z-.:]{1,})?$') 37 | 38 | const isIPv4 = exports.isIPv4 = function isIPv4 (host) { 39 | return IPv4Pattern.test(host) 40 | } 41 | 42 | const isIPv6 = exports.isIPv6 = function isIPv6 (host) { 43 | return IPv6Pattern.test(host) 44 | } 45 | 46 | exports.isIP = function isIP (host) { 47 | if (isIPv4(host)) return 4 48 | if (isIPv6(host)) return 6 49 | return 0 50 | } 51 | -------------------------------------------------------------------------------- /lib/network-interfaces.js: -------------------------------------------------------------------------------- 1 | const events = require('events') 2 | const b4a = require('b4a') 3 | const binding = require('../binding') 4 | 5 | module.exports = class NetworkInterfaces extends events.EventEmitter { 6 | constructor (udx) { 7 | super() 8 | 9 | this._handle = b4a.allocUnsafe(binding.sizeof_udx_napi_interface_event_t) 10 | this._watching = false 11 | this._destroying = null 12 | 13 | binding.udx_napi_interface_event_init(udx._handle, this._handle, this, 14 | this._onevent, 15 | this._onclose 16 | ) 17 | 18 | this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle) 19 | } 20 | 21 | _onclose () { 22 | this.emit('close') 23 | } 24 | 25 | _onevent () { 26 | this.interfaces = binding.udx_napi_interface_event_get_addrs(this._handle) 27 | 28 | this.emit('change', this.interfaces) 29 | } 30 | 31 | watch () { 32 | if (this._watching) return this 33 | this._watching = true 34 | 35 | binding.udx_napi_interface_event_start(this._handle) 36 | 37 | return this 38 | } 39 | 40 | unwatch () { 41 | if (!this._watching) return this 42 | this._watching = false 43 | 44 | binding.udx_napi_interface_event_stop(this._handle) 45 | 46 | return this 47 | } 48 | 49 | async destroy () { 50 | if (this._destroying) return this._destroying 51 | this._destroying = events.once(this, 'close') 52 | 53 | binding.udx_napi_interface_event_close(this._handle) 54 | 55 | return this._destroying 56 | } 57 | 58 | [Symbol.iterator] () { 59 | return this.interfaces[Symbol.iterator]() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/socket.js: -------------------------------------------------------------------------------- 1 | const events = require('events') 2 | const b4a = require('b4a') 3 | const binding = require('../binding') 4 | const ip = require('./ip') 5 | 6 | module.exports = class UDXSocket extends events.EventEmitter { 7 | constructor (udx, opts = {}) { 8 | super() 9 | 10 | this.udx = udx 11 | 12 | this._handle = b4a.allocUnsafe(binding.sizeof_udx_napi_socket_t) 13 | this._inited = false 14 | this._host = null 15 | this._family = 0 16 | this._ipv6Only = opts.ipv6Only === true 17 | this._reuseAddress = opts.reuseAddress === true 18 | this._port = 0 19 | this._reqs = [] 20 | this._free = [] 21 | this._closing = null 22 | this._closed = false 23 | 24 | this._view64 = new BigUint64Array(this._handle.buffer, this._handle.byteOffset, this._handle.byteLength >> 3) 25 | 26 | this.streams = new Set() 27 | 28 | this.userData = null 29 | } 30 | 31 | get bound () { 32 | return this._port !== 0 33 | } 34 | 35 | get closing () { 36 | return this._closing !== null 37 | } 38 | 39 | get idle () { 40 | return this.streams.size === 0 41 | } 42 | 43 | get busy () { 44 | return this.streams.size > 0 45 | } 46 | 47 | get bytesTransmitted () { 48 | if (this._inited !== true) return 0 49 | return Number(this._view64[binding.offsetof_udx_socket_t_bytes_tx >> 3]) 50 | } 51 | 52 | get packetsTransmitted () { 53 | if (this._inited !== true) return 0 54 | return Number(this._view64[binding.offsetof_udx_socket_t_packets_tx >> 3]) 55 | } 56 | 57 | get bytesReceived () { 58 | if (this._inited !== true) return 0 59 | return Number(this._view64[binding.offsetof_udx_socket_t_bytes_rx >> 3]) 60 | } 61 | 62 | get packetsReceived () { 63 | if (this._inited !== true) return 0 64 | return Number(this._view64[binding.offsetof_udx_socket_t_packets_rx >> 3]) 65 | } 66 | 67 | get packetsDroppedByKernel () { 68 | if (this._inited !== true) return 0 69 | return Number(this._view64[binding.offsetof_udx_socket_t_packets_dropped_by_kernel >> 3]) 70 | } 71 | 72 | toJSON () { 73 | return { 74 | bound: this.bound, 75 | closing: this.closing, 76 | streams: this.streams.size, 77 | address: this.address(), 78 | ipv6Only: this._ipv6Only, 79 | reuseAddress: this._reuseAddress, 80 | idle: this.idle, 81 | busy: this.busy 82 | } 83 | } 84 | 85 | _init () { 86 | if (this._inited) return 87 | 88 | binding.udx_napi_socket_init(this.udx._handle, this._handle, this, 89 | this._onsend, 90 | this._onmessage, 91 | this._onclose, 92 | this._reallocMessage 93 | ) 94 | 95 | this._inited = true 96 | } 97 | 98 | _onsend (id, err) { 99 | const req = this._reqs[id] 100 | 101 | const onflush = req.onflush 102 | 103 | req.buffer = null 104 | req.onflush = null 105 | 106 | this._free.push(id) 107 | 108 | onflush(err >= 0) 109 | 110 | // gc the free list 111 | if (this._free.length >= 16 && this._free.length === this._reqs.length) { 112 | this._free = [] 113 | this._reqs = [] 114 | } 115 | } 116 | 117 | _onmessage (len, port, host, family) { 118 | this.emit('message', this.udx._consumeMessage(len), { host, family, port }) 119 | return this.udx._buffer 120 | } 121 | 122 | _onclose () { 123 | this.emit('close') 124 | } 125 | 126 | _reallocMessage () { 127 | return this.udx._reallocMessage() 128 | } 129 | 130 | _onidle () { 131 | this.emit('idle') 132 | } 133 | 134 | _onbusy () { 135 | this.emit('busy') 136 | } 137 | 138 | _addStream (stream) { 139 | if (this.streams.has(stream)) return false 140 | this.streams.add(stream) 141 | if (this.streams.size === 1) this._onbusy() 142 | return true 143 | } 144 | 145 | _removeStream (stream) { 146 | if (!this.streams.has(stream)) return false 147 | this.streams.delete(stream) 148 | const closed = this._closeMaybe() 149 | if (this.idle && !closed) this._onidle() 150 | return true 151 | } 152 | 153 | address () { 154 | if (!this.bound) return null 155 | return { host: this._host, family: this._family, port: this._port } 156 | } 157 | 158 | bind (port, host) { 159 | if (this.bound) throw new Error('Already bound') 160 | if (this.closing) throw new Error('Socket is closed') 161 | 162 | if (!port) port = 0 163 | 164 | let flags = 0 165 | if (this._ipv6Only) flags |= binding.UV_UDP_IPV6ONLY 166 | if (this._reuseAddress) flags |= binding.UV_UDP_REUSEADDR 167 | 168 | let family 169 | 170 | if (host) { 171 | family = ip.isIP(host) 172 | if (!family) throw new Error(`${host} is not a valid IP address`) 173 | 174 | if (!this._inited) this._init() 175 | 176 | this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags) 177 | } else { 178 | if (!this._inited) this._init() 179 | 180 | try { 181 | host = '::' 182 | family = 6 183 | this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags) 184 | } catch { 185 | host = '0.0.0.0' 186 | family = 4 187 | this._port = binding.udx_napi_socket_bind(this._handle, port, host, family, flags) 188 | } 189 | } 190 | 191 | this._host = host 192 | this._family = family 193 | 194 | this.emit('listening') 195 | } 196 | 197 | async close () { 198 | if (this._closing) return this._closing 199 | this._closing = new Promise(resolve => this.once('close', resolve)) 200 | this._closeMaybe() 201 | return this._closing 202 | } 203 | 204 | _closeMaybe () { 205 | if (this._closed || this._closing === null) return this._closed 206 | 207 | if (!this._inited) { 208 | this._closed = true 209 | this.emit('close') 210 | return true 211 | } 212 | 213 | if (this.idle) { 214 | binding.udx_napi_socket_close(this._handle) 215 | this._closed = true 216 | } 217 | 218 | return this._closed 219 | } 220 | 221 | setTTL (ttl) { 222 | if (!this._inited) throw new Error('Socket not active') 223 | binding.udx_napi_socket_set_ttl(this._handle, ttl) 224 | } 225 | 226 | getRecvBufferSize () { 227 | if (!this._inited) throw new Error('Socket not active') 228 | return binding.udx_napi_socket_get_recv_buffer_size(this._handle) 229 | } 230 | 231 | setRecvBufferSize (size) { 232 | if (!this._inited) throw new Error('Socket not active') 233 | return binding.udx_napi_socket_set_recv_buffer_size(this._handle, size) 234 | } 235 | 236 | getSendBufferSize () { 237 | if (!this._inited) throw new Error('Socket not active') 238 | return binding.udx_napi_socket_get_send_buffer_size(this._handle) 239 | } 240 | 241 | setSendBufferSize (size) { 242 | if (!this._inited) throw new Error('Socket not active') 243 | return binding.udx_napi_socket_set_send_buffer_size(this._handle, size) 244 | } 245 | 246 | addMembership (group, ifaceAddress) { 247 | if (!this._inited) throw new Error('Socket not active') 248 | return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', true) 249 | } 250 | 251 | dropMembership (group, ifaceAddress) { 252 | if (!this._inited) throw new Error('Socket not active') 253 | return binding.udx_napi_socket_set_membership(this._handle, group, ifaceAddress || '', false) 254 | } 255 | 256 | async send (buffer, port, host, ttl) { 257 | if (this.closing) return false 258 | 259 | if (!host) host = '127.0.0.1' 260 | 261 | const family = ip.isIP(host) 262 | if (!family) throw new Error(`${host} is not a valid IP address`) 263 | 264 | if (!this.bound) this.bind(0) 265 | 266 | const id = this._allocSend() 267 | const req = this._reqs[id] 268 | 269 | req.buffer = buffer 270 | 271 | const promise = new Promise((resolve) => { 272 | req.onflush = resolve 273 | }) 274 | 275 | binding.udx_napi_socket_send_ttl(this._handle, req.handle, id, buffer, port, host, family, ttl || 0) 276 | 277 | return promise 278 | } 279 | 280 | trySend (buffer, port, host, ttl) { 281 | if (this.closing) return 282 | 283 | if (!host) host = '127.0.0.1' 284 | 285 | const family = ip.isIP(host) 286 | if (!family) throw new Error(`${host} is not a valid IP address`) 287 | 288 | if (!this.bound) this.bind(0) 289 | 290 | const id = this._allocSend() 291 | const req = this._reqs[id] 292 | 293 | req.buffer = buffer 294 | req.onflush = noop 295 | 296 | binding.udx_napi_socket_send_ttl(this._handle, req.handle, id, buffer, port, host, family, ttl || 0) 297 | } 298 | 299 | _allocSend () { 300 | if (this._free.length > 0) return this._free.pop() 301 | const handle = b4a.allocUnsafe(binding.sizeof_udx_socket_send_t) 302 | return this._reqs.push({ handle, buffer: null, onflush: null }) - 1 303 | } 304 | } 305 | 306 | function noop () {} 307 | -------------------------------------------------------------------------------- /lib/stream.js: -------------------------------------------------------------------------------- 1 | const streamx = require('streamx') 2 | const b4a = require('b4a') 3 | const binding = require('../binding') 4 | const ip = require('./ip') 5 | 6 | const MAX_PACKET = 2048 7 | const BUFFER_SIZE = 65536 + MAX_PACKET 8 | 9 | module.exports = class UDXStream extends streamx.Duplex { 10 | constructor (udx, id, opts = {}) { 11 | super({ mapWritable: toBuffer, eagerOpen: true }) 12 | 13 | this.udx = udx 14 | this.socket = null 15 | 16 | this._handle = b4a.allocUnsafe(binding.sizeof_udx_napi_stream_t) 17 | this._view = new Uint32Array(this._handle.buffer, this._handle.byteOffset, this._handle.byteLength >> 2) 18 | this._view16 = new Uint16Array(this._handle.buffer, this._handle.byteOffset, this._handle.byteLength >> 1) 19 | this._view64 = new BigUint64Array(this._handle.buffer, this._handle.byteOffset, this._handle.byteLength >> 3) 20 | 21 | this._wreqs = [] 22 | this._wfree = [] 23 | 24 | this._sreqs = [] 25 | this._sfree = [] 26 | this._closed = false 27 | 28 | this._flushing = 0 29 | this._flushes = [] 30 | 31 | this._buffer = null 32 | this._reallocData() 33 | 34 | this._onwrite = null 35 | this._ondestroy = null 36 | this._firewall = opts.firewall || firewallAll 37 | 38 | this._remoteChanging = null 39 | this._previousSocket = null 40 | 41 | this.id = id 42 | this.remoteId = 0 43 | this.remoteHost = null 44 | this.remoteFamily = 0 45 | this.remotePort = 0 46 | 47 | this.userData = null 48 | 49 | binding.udx_napi_stream_init(this.udx._handle, this._handle, id, opts.framed ? 1 : 0, this, 50 | this._ondata, 51 | this._onend, 52 | this._ondrain, 53 | this._onack, 54 | this._onsend, 55 | this._onmessage, 56 | this._onclose, 57 | this._onfirewall, 58 | this._onremotechanged, 59 | this._reallocData, 60 | this._reallocMessage 61 | ) 62 | 63 | if (opts.seq) binding.udx_napi_stream_set_seq(this._handle, opts.seq) 64 | 65 | binding.udx_napi_stream_recv_start(this._handle, this._buffer) 66 | } 67 | 68 | get connected () { 69 | return this.socket !== null 70 | } 71 | 72 | get mtu () { 73 | return this._view16[binding.offsetof_udx_stream_t_mtu >> 1] 74 | } 75 | 76 | get rtt () { 77 | return this._view[binding.offsetof_udx_stream_t_srtt >> 2] 78 | } 79 | 80 | get cwnd () { 81 | return this._view[binding.offsetof_udx_stream_t_cwnd >> 2] 82 | } 83 | 84 | get rtoCount () { 85 | return this._view16[binding.offsetof_udx_stream_t_rto_count >> 1] 86 | } 87 | 88 | get retransmits () { 89 | return this._view16[binding.offsetof_udx_stream_t_retransmit_count >> 1] 90 | } 91 | 92 | get fastRecoveries () { 93 | return this._view16[binding.offsetof_udx_stream_t_fast_recovery_count >> 1] 94 | } 95 | 96 | get inflight () { 97 | return this._view[binding.offsetof_udx_stream_t_inflight >> 2] 98 | } 99 | 100 | get bytesTransmitted () { 101 | return Number(this._view64[binding.offsetof_udx_stream_t_bytes_tx >> 3]) 102 | } 103 | 104 | get packetsTransmitted () { 105 | return Number(this._view64[binding.offsetof_udx_stream_t_packets_tx >> 3]) 106 | } 107 | 108 | get bytesReceived () { 109 | return Number(this._view64[binding.offsetof_udx_stream_t_bytes_rx >> 3]) 110 | } 111 | 112 | get packetsReceived () { 113 | return Number(this._view64[binding.offsetof_udx_stream_t_packets_rx >> 3]) 114 | } 115 | 116 | get localHost () { 117 | return this.socket ? this.socket.address().host : null 118 | } 119 | 120 | get localFamily () { 121 | return this.socket ? this.socket.address().family : 0 122 | } 123 | 124 | get localPort () { 125 | return this.socket ? this.socket.address().port : 0 126 | } 127 | 128 | setInteractive (bool) { 129 | if (!this._closed) return 130 | binding.udx_napi_stream_set_mode(this._handle, bool ? 0 : 1) 131 | } 132 | 133 | connect (socket, remoteId, port, host, opts = {}) { 134 | if (this._closed) return 135 | 136 | if (this.connected) throw new Error('Already connected') 137 | if (socket.closing) throw new Error('Socket is closed') 138 | 139 | if (typeof host === 'object') { 140 | opts = host 141 | host = null 142 | } 143 | 144 | if (!host) host = '127.0.0.1' 145 | 146 | const family = ip.isIP(host) 147 | if (!family) throw new Error(`${host} is not a valid IP address`) 148 | if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`) 149 | 150 | if (!socket.bound) socket.bind(0) 151 | 152 | this.remoteId = remoteId 153 | this.remotePort = port 154 | this.remoteHost = host 155 | this.remoteFamily = family 156 | this.socket = socket 157 | 158 | if (opts.ack) binding.udx_napi_stream_set_ack(this._handle, opts.ack) 159 | 160 | binding.udx_napi_stream_connect(this._handle, socket._handle, remoteId, port, host, family) 161 | 162 | this.socket._addStream(this) 163 | 164 | this.emit('connect') 165 | } 166 | 167 | changeRemote (socket, remoteId, port, host) { 168 | if (this._remoteChanging) throw new Error('Remote already changing') 169 | 170 | if (!this.connected) throw new Error('Not yet connected') 171 | if (socket.closing) throw new Error('Socket is closed') 172 | 173 | if (this.socket.udx !== socket.udx) { 174 | throw new Error('Cannot change to a socket on another UDX instance') 175 | } 176 | 177 | if (!host) host = '127.0.0.1' 178 | 179 | const family = ip.isIP(host) 180 | if (!family) throw new Error(`${host} is not a valid IP address`) 181 | if (!(port > 0 && port < 65536)) throw new Error(`${port} is not a valid port`) 182 | 183 | if (this.socket !== socket) this._previousSocket = this.socket 184 | 185 | this.remoteId = remoteId 186 | this.remotePort = port 187 | this.remoteHost = host 188 | this.remoteFamily = family 189 | this.socket = socket 190 | 191 | this._remoteChanging = new Promise((resolve, reject) => { 192 | const onchanged = () => { 193 | this.off('close', onclose) 194 | resolve() 195 | } 196 | 197 | const onclose = () => { 198 | this.off('remote-changed', onchanged) 199 | reject(new Error('Stream is closed')) 200 | } 201 | 202 | this 203 | .once('remote-changed', onchanged) 204 | .once('close', onclose) 205 | }) 206 | 207 | binding.udx_napi_stream_change_remote(this._handle, socket._handle, remoteId, port, host, family) 208 | 209 | this.socket._addStream(this) 210 | 211 | return this._remoteChanging 212 | } 213 | 214 | relayTo (destination) { 215 | if (this._closed) return 216 | 217 | binding.udx_napi_stream_relay_to(this._handle, destination._handle) 218 | } 219 | 220 | async send (buffer) { 221 | if (!this.connected || this._closed) return false 222 | 223 | const id = this._allocSend() 224 | const req = this._sreqs[id] 225 | 226 | req.buffer = buffer 227 | 228 | const promise = new Promise((resolve) => { 229 | req.onflush = resolve 230 | }) 231 | 232 | binding.udx_napi_stream_send(this._handle, req.handle, id, buffer) 233 | 234 | return promise 235 | } 236 | 237 | trySend (buffer) { 238 | if (!this.connected || this._closed) return 239 | 240 | const id = this._allocSend() 241 | const req = this._sreqs[id] 242 | 243 | req.buffer = buffer 244 | req.onflush = noop 245 | 246 | binding.udx_napi_stream_send(this._handle, req.handle, id, buffer) 247 | } 248 | 249 | async flush () { 250 | if ((await streamx.Writable.drained(this)) === false) return false 251 | if (this.destroying) return false 252 | 253 | const missing = this._wreqs.length - this._wfree.length 254 | if (missing === 0) return true 255 | 256 | return new Promise((resolve) => { 257 | this._flushes.push({ flush: this._flushing++, missing, resolve }) 258 | }) 259 | } 260 | 261 | toJSON () { 262 | return { 263 | id: this.id, 264 | connected: this.connected, 265 | destroying: this.destroying, 266 | destroyed: this.destroyed, 267 | remoteId: this.remoteId, 268 | remoteHost: this.remoteHost, 269 | remoteFamily: this.remoteFamily, 270 | remotePort: this.remotePort, 271 | mtu: this.mtu, 272 | rtt: this.rtt, 273 | cwnd: this.cwnd, 274 | inflight: this.inflight, 275 | socket: this.socket ? this.socket.toJSON() : null 276 | } 277 | } 278 | 279 | _read (cb) { 280 | cb(null) 281 | } 282 | 283 | _writeContinue (err) { 284 | if (this._onwrite === null) return 285 | const cb = this._onwrite 286 | this._onwrite = null 287 | cb(err) 288 | } 289 | 290 | _destroyContinue (err) { 291 | if (this._ondestroy === null) return 292 | const cb = this._ondestroy 293 | this._ondestroy = null 294 | cb(err) 295 | } 296 | 297 | _writev (buffers, cb) { 298 | if (!this.connected) throw customError('Writing while not connected not currently supported', 'ERR_ASSERTION') 299 | 300 | let drained = true 301 | 302 | if (buffers.length === 1) { 303 | const id = this._allocWrite(1) 304 | const req = this._wreqs[id] 305 | 306 | req.flush = this._flushing 307 | req.buffer = buffers[0] 308 | 309 | drained = binding.udx_napi_stream_write(this._handle, req.handle, id, req.buffer) !== 0 310 | } else { 311 | const id = this._allocWrite(nextBatchSize(buffers.length)) 312 | const req = this._wreqs[id] 313 | 314 | req.flush = this._flushing 315 | req.buffers = buffers 316 | 317 | drained = binding.udx_napi_stream_writev(this._handle, req.handle, id, req.buffers) !== 0 318 | } 319 | 320 | if (drained) cb(null) 321 | else this._onwrite = cb 322 | } 323 | 324 | _final (cb) { 325 | const id = this._allocWrite(1) 326 | const req = this._wreqs[id] 327 | 328 | req.flush = this._flushes 329 | req.buffer = b4a.allocUnsafe(0) 330 | 331 | const drained = binding.udx_napi_stream_write_end(this._handle, req.handle, id, req.buffer) !== 0 332 | 333 | if (drained) cb(null) 334 | else this._onwrite = cb 335 | } 336 | 337 | _predestroy () { 338 | if (!this._closed) binding.udx_napi_stream_destroy(this._handle) 339 | this._closed = true 340 | this._writeContinue(null) 341 | } 342 | 343 | _destroy (cb) { 344 | if (this.connected) this._ondestroy = cb 345 | else cb(null) 346 | } 347 | 348 | _ondata (read) { 349 | this.push(this._consumeData(read)) 350 | return this._buffer 351 | } 352 | 353 | _onend (read) { 354 | if (read > 0) this.push(this._consumeData(read)) 355 | this.push(null) 356 | } 357 | 358 | _ondrain () { 359 | this._writeContinue(null) 360 | } 361 | 362 | _flushAck (flush) { 363 | for (let i = this._flushes.length - 1; i >= 0; i--) { 364 | const f = this._flushes[i] 365 | if (f.flush < flush) break 366 | f.missing-- 367 | } 368 | 369 | while (this._flushes.length > 0 && this._flushes[0].missing === 0) { 370 | this._flushes.shift().resolve(true) 371 | } 372 | } 373 | 374 | _onack (id) { 375 | const req = this._wreqs[id] 376 | 377 | req.buffers = req.buffer = null 378 | this._wfree.push(id) 379 | 380 | if (this._flushes.length > 0) this._flushAck(req.flush) 381 | 382 | // gc the free list 383 | if (this._wfree.length >= 64 && this._wfree.length === this._wreqs.length) { 384 | this._wfree = [] 385 | this._wreqs = [] 386 | } 387 | } 388 | 389 | _onsend (id, err) { 390 | const req = this._sreqs[id] 391 | 392 | const onflush = req.onflush 393 | 394 | req.buffer = null 395 | req.onflush = null 396 | 397 | this._sfree.push(id) 398 | 399 | onflush(err >= 0) 400 | 401 | // gc the free list 402 | if (this._sfree.length >= 16 && this._sfree.length === this._sreqs.length) { 403 | this._sfree = [] 404 | this._sreqs = [] 405 | } 406 | } 407 | 408 | _onmessage (len) { 409 | this.emit('message', this.udx._consumeMessage(len)) 410 | return this.udx._buffer 411 | } 412 | 413 | _onclose (err) { 414 | this._closed = true 415 | 416 | if (this.socket) { 417 | this.socket._removeStream(this) 418 | this.socket = null 419 | } 420 | 421 | if (this._previousSocket) { 422 | this._previousSocket._removeStream(this) 423 | this._previousSocket = null 424 | } 425 | 426 | // no error, we don't need to do anything 427 | if (!err) return this._destroyContinue(null) 428 | 429 | if (this._ondestroy === null) this.destroy(err) 430 | else this._destroyContinue(err) 431 | } 432 | 433 | _onfirewall (socket, port, host, family) { 434 | return this._firewall(socket, port, host, family) ? 1 : 0 435 | } 436 | 437 | _onremotechanged () { 438 | if (this._previousSocket) { 439 | this._previousSocket._removeStream(this) 440 | this._previousSocket = null 441 | } 442 | 443 | this._remoteChanging = null 444 | this.emit('remote-changed') 445 | } 446 | 447 | _consumeData (len) { 448 | const next = this._buffer.subarray(0, len) 449 | this._buffer = this._buffer.subarray(len) 450 | if (this._buffer.byteLength < MAX_PACKET) this._reallocData() 451 | return next 452 | } 453 | 454 | _reallocData () { 455 | this._buffer = b4a.allocUnsafe(BUFFER_SIZE) 456 | return this._buffer 457 | } 458 | 459 | _reallocMessage () { 460 | return this.udx._reallocMessage() 461 | } 462 | 463 | _allocWrite (size) { 464 | if (this._wfree.length === 0) { 465 | const handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size)) 466 | return this._wreqs.push({ handle, size, buffers: null, buffer: null, flush: 0 }) - 1 467 | } 468 | 469 | const free = this._wfree.pop() 470 | if (size === 1) return free 471 | 472 | const next = this._wreqs[free] 473 | if (next.size < size) { 474 | next.handle = b4a.allocUnsafe(binding.udx_napi_stream_write_sizeof(size)) 475 | next.size = size 476 | } 477 | 478 | return free 479 | } 480 | 481 | _allocSend () { 482 | if (this._sfree.length > 0) return this._sfree.pop() 483 | const handle = b4a.allocUnsafe(binding.sizeof_udx_stream_send_t) 484 | return this._sreqs.push({ handle, buffer: null, resolve: null, reject: null }) - 1 485 | } 486 | } 487 | 488 | function noop () {} 489 | 490 | function toBuffer (data) { 491 | return typeof data === 'string' ? b4a.from(data) : data 492 | } 493 | 494 | function firewallAll (socket, port, host) { 495 | return true 496 | } 497 | 498 | function customError (message, code) { 499 | const error = new Error(message) 500 | error.code = code 501 | return error 502 | } 503 | 504 | function nextBatchSize (n) { // try to coerce the the writevs into sameish size 505 | if (n === 1) return 1 506 | // group all < 8 to the same size, low mem overhead but save some small allocs 507 | if (n < 8) return 8 508 | if (n < 16) return 16 509 | if (n < 32) return 32 510 | if (n < 64) return 64 511 | return n 512 | } 513 | -------------------------------------------------------------------------------- /lib/udx.js: -------------------------------------------------------------------------------- 1 | const b4a = require('b4a') 2 | const binding = require('../binding') 3 | const ip = require('./ip') 4 | const Socket = require('./socket') 5 | const Stream = require('./stream') 6 | const NetworkInterfaces = require('./network-interfaces') 7 | 8 | const MAX_MESSAGE = 4096 9 | const BUFFER_SIZE = 65536 + MAX_MESSAGE 10 | 11 | module.exports = class UDX { 12 | constructor () { 13 | this._handle = b4a.allocUnsafe(binding.sizeof_udx_napi_t) 14 | this._watchers = new Set() 15 | this._view64 = new BigUint64Array(this._handle.buffer, this._handle.byteOffset, this._handle.byteLength >> 3) 16 | 17 | this._buffer = null 18 | this._reallocMessage() 19 | 20 | binding.udx_napi_init(this._handle, this._buffer) 21 | } 22 | 23 | static isIPv4 (host) { 24 | return ip.isIPv4(host) 25 | } 26 | 27 | static isIPv6 (host) { 28 | return ip.isIPv6(host) 29 | } 30 | 31 | static isIP (host) { 32 | return ip.isIP(host) 33 | } 34 | 35 | get bytesTransmitted () { 36 | return Number(this._view64[binding.offsetof_udx_t_bytes_tx >> 3]) 37 | } 38 | 39 | get packetsTransmitted () { 40 | return Number(this._view64[binding.offsetof_udx_t_packets_tx >> 3]) 41 | } 42 | 43 | get bytesReceived () { 44 | return Number(this._view64[binding.offsetof_udx_t_bytes_rx >> 3]) 45 | } 46 | 47 | get packetsReceived () { 48 | return Number(this._view64[binding.offsetof_udx_t_packets_rx >> 3]) 49 | } 50 | 51 | get packetsDroppedByKernel () { 52 | return Number(this._view64[binding.offsetof_udx_t_packets_dropped_by_kernel >> 3]) 53 | } 54 | 55 | _consumeMessage (len) { 56 | const next = this._buffer.subarray(0, len) 57 | this._buffer = this._buffer.subarray(len) 58 | if (this._buffer.byteLength < MAX_MESSAGE) this._reallocMessage() 59 | return next 60 | } 61 | 62 | _reallocMessage () { 63 | this._buffer = b4a.allocUnsafe(BUFFER_SIZE) 64 | return this._buffer 65 | } 66 | 67 | createSocket (opts) { 68 | return new Socket(this, opts) 69 | } 70 | 71 | createStream (id, opts) { 72 | return new Stream(this, id, opts) 73 | } 74 | 75 | networkInterfaces () { 76 | let [watcher = null] = this._watchers 77 | if (watcher) return watcher.interfaces 78 | 79 | watcher = new NetworkInterfaces(this) 80 | watcher.destroy() 81 | 82 | return watcher.interfaces 83 | } 84 | 85 | watchNetworkInterfaces (onchange) { 86 | const watcher = new NetworkInterfaces(this) 87 | 88 | this._watchers.add(watcher) 89 | watcher.on('close', () => { 90 | this._watchers.delete(watcher) 91 | }) 92 | 93 | if (onchange) watcher.on('change', onchange) 94 | 95 | return watcher.watch() 96 | } 97 | 98 | async lookup (host, opts = {}) { 99 | const { 100 | family = 0 101 | } = opts 102 | 103 | const req = b4a.allocUnsafe(binding.sizeof_udx_napi_lookup_t) 104 | const ctx = { 105 | req, 106 | resolve: null, 107 | reject: null 108 | } 109 | 110 | const promise = new Promise((resolve, reject) => { 111 | ctx.resolve = resolve 112 | ctx.reject = reject 113 | }) 114 | 115 | binding.udx_napi_lookup(this._handle, req, host, family, ctx, onlookup) 116 | 117 | return promise 118 | } 119 | } 120 | 121 | function onlookup (err, host, family) { 122 | if (err) this.reject(err) 123 | else this.resolve({ host, family }) 124 | } 125 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "udx-native", 3 | "version": "1.17.8", 4 | "description": "udx is reliable, multiplexed, and congestion-controlled streams over udp", 5 | "main": "lib/udx.js", 6 | "files": [ 7 | "lib", 8 | "prebuilds", 9 | "binding.c", 10 | "binding.js", 11 | "CMakeLists.txt" 12 | ], 13 | "imports": { 14 | "events": { 15 | "bare": "bare-events", 16 | "default": "events" 17 | } 18 | }, 19 | "addon": true, 20 | "scripts": { 21 | "test": "npm run lint && npm run test:bare && npm run test:node", 22 | "test:node": "node test/all.js", 23 | "test:bare": "bare test/all.js", 24 | "test:all": "brittle test/*.js test/slow/*.js", 25 | "test:generate": "brittle -r test/all.js test/*.js", 26 | "bench": "brittle test/bench/*.js", 27 | "lint": "standard" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/holepunchto/udx-native.git" 32 | }, 33 | "keywords": [ 34 | "tcp", 35 | "udp", 36 | "stream", 37 | "reliable" 38 | ], 39 | "author": "Holepunch Inc.", 40 | "license": "Apache-2.0", 41 | "gypfile": true, 42 | "bugs": { 43 | "url": "https://github.com/holepunchto/udx-native/issues" 44 | }, 45 | "homepage": "https://github.com/holepunchto/udx-native#readme", 46 | "dependencies": { 47 | "b4a": "^1.5.0", 48 | "bare-events": "^2.2.0", 49 | "require-addon": "^1.1.0", 50 | "streamx": "^2.14.0" 51 | }, 52 | "devDependencies": { 53 | "brittle": "^3.1.0", 54 | "cmake-bare": "^1.1.10", 55 | "cmake-fetch": "^1.0.1", 56 | "cmake-napi": "^1.0.5", 57 | "is-ci": "^3.0.1", 58 | "standard": "^17.1.0", 59 | "tiny-byte-size": "^1.1.0" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /test/all.js: -------------------------------------------------------------------------------- 1 | // This runner is auto-generated by Brittle 2 | 3 | runTests() 4 | 5 | async function runTests () { 6 | const test = (await import('brittle')).default 7 | 8 | test.pause() 9 | 10 | await import('./socket.js') 11 | await import('./stream-framed.js') 12 | await import('./stream-parallel.js') 13 | await import('./stream.js') 14 | await import('./udx.js') 15 | 16 | test.resume() 17 | } 18 | -------------------------------------------------------------------------------- /test/bench/basic.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const byteSize = require('tiny-byte-size') 3 | const { makePairs, pipeStreamPairs } = require('../helpers') 4 | 5 | const STREAM_COUNTS = [1, 2, 4, 8] 6 | const MESSAGE_SIZES = [1024 * 64, 1024] 7 | const TRANSFER_SIZE = 1024 * 1024 * 64// send 64MB in total 8 | 9 | for (const messageSize of MESSAGE_SIZES) { 10 | for (const streamCount of STREAM_COUNTS) { 11 | test(`throughput, ${streamCount} streams, 1 socket, message size ${messageSize}`, async t => { 12 | await benchmarkThroughput(t, streamCount, 'single', messageSize, TRANSFER_SIZE) 13 | }) 14 | if (streamCount > 1) { 15 | test(`throughput, ${streamCount} streams, ${streamCount} sockets, message size ${messageSize}`, async t => { 16 | await benchmarkThroughput(t, streamCount, 'multi', messageSize, TRANSFER_SIZE) 17 | }) 18 | } 19 | } 20 | } 21 | 22 | async function benchmarkThroughput (t, streamCount, multiplexMode, messageSize, total) { 23 | const { streams, close } = makePairs(streamCount, multiplexMode) 24 | const limit = total / streamCount 25 | 26 | const elapsed = await t.execution(async () => { 27 | try { 28 | await pipeStreamPairs(streams, messageSize, limit) 29 | } finally { 30 | close() 31 | } 32 | }) 33 | 34 | t.comment(byteSize.perSecond(total, elapsed)) 35 | } 36 | -------------------------------------------------------------------------------- /test/bench/latency.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const b4a = require('b4a') 3 | const byteSize = require('tiny-byte-size') 4 | const UDX = require('../..') 5 | const proxy = require('../helpers/proxy') 6 | 7 | test('throughput, 600 ms latency ± 100 ms jitter', async (t) => { 8 | const udx = new UDX() 9 | 10 | const aSocket = udx.createSocket() 11 | aSocket.bind() 12 | 13 | const bSocket = udx.createSocket() 14 | bSocket.bind() 15 | 16 | const a = udx.createStream(1) 17 | const b = udx.createStream(2) 18 | 19 | const socket = await proxy({ from: aSocket, to: bSocket }, async () => { 20 | await delay() 21 | }) 22 | 23 | a.connect(aSocket, 2, socket.address().port) 24 | b.connect(bSocket, 1, socket.address().port) 25 | 26 | let received = 0 27 | 28 | const elapsed = await t.execution(new Promise((resolve, reject) => { 29 | a 30 | .on('error', reject) 31 | .end(b4a.alloc(32768)) 32 | 33 | b 34 | .on('error', reject) 35 | .on('data', (data) => { 36 | received += data.byteLength 37 | 38 | if (received === 32768) resolve() 39 | }) 40 | .on('end', async () => { 41 | a.destroy() 42 | b.destroy() 43 | 44 | await aSocket.close() 45 | await bSocket.close() 46 | 47 | socket.close() 48 | }) 49 | })) 50 | 51 | t.comment(byteSize.perSecond(received, elapsed)) 52 | }) 53 | 54 | function delay () { 55 | return new Promise((resolve) => { 56 | setTimeout(resolve, 500 + /* jitter */ (Math.random() * 200 | 0)) 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /test/helpers/index.js: -------------------------------------------------------------------------------- 1 | const b4a = require('b4a') 2 | const UDX = require('../../') 3 | 4 | module.exports = { makeTwoStreams, makePairs, pipeStreamPairs, uncaught, createSocket } 5 | 6 | function uncaught (fn) { 7 | if (global.Bare) { 8 | global.Bare.once('uncaughtException', fn) 9 | } else { 10 | process.once('uncaughtException', fn) 11 | } 12 | } 13 | 14 | function createSocket (t, udx, opts) { 15 | const socket = udx.createSocket(opts) 16 | const closed = new Promise(resolve => socket.once('close', resolve)) 17 | t.teardown(() => closed, { order: Infinity }) 18 | return socket 19 | } 20 | 21 | function makeTwoStreams (t, opts) { 22 | const a = new UDX() 23 | const b = new UDX() 24 | 25 | const aSocket = createSocket(t, a) 26 | const bSocket = createSocket(t, b) 27 | 28 | aSocket.bind(0, '127.0.0.1') 29 | bSocket.bind(0, '127.0.0.1') 30 | 31 | const aStream = a.createStream(1, opts) 32 | const bStream = b.createStream(2, opts) 33 | 34 | aStream.connect(aSocket, bStream.id, bSocket.address().port, '127.0.0.1') 35 | bStream.connect(bSocket, aStream.id, aSocket.address().port, '127.0.0.1') 36 | 37 | t.teardown(() => { 38 | aSocket.close() 39 | bSocket.close() 40 | }) 41 | 42 | return [aStream, bStream] 43 | } 44 | 45 | function makePairs (n, multiplexMode = 'single') { 46 | const ua = new UDX() 47 | const ub = new UDX() 48 | 49 | let id = 1 50 | const sockets = [] 51 | const streams = [] 52 | let a, b 53 | if (multiplexMode === 'single') { 54 | a = ua.createSocket() 55 | b = ub.createSocket() 56 | a.bind(0, '127.0.0.1') 57 | b.bind(0, '127.0.0.1') 58 | sockets.push(a, b) 59 | } 60 | while (streams.length < n) { 61 | let sa, sb 62 | if (multiplexMode === 'single') { 63 | sa = a 64 | sb = b 65 | } else { 66 | sa = ua.createSocket() 67 | sb = ub.createSocket() 68 | sa.bind(0, '127.0.0.1') 69 | sb.bind(0, '127.0.0.1') 70 | sockets.push(sa, sb) 71 | } 72 | const streamId = id++ 73 | const aStream = ua.createStream(streamId) 74 | const bStream = ub.createStream(streamId) 75 | aStream.connect(sa, bStream.id, sb.address().port, '127.0.0.1') 76 | bStream.connect(sb, aStream.id, sa.address().port, '127.0.0.1') 77 | streams.push([aStream, bStream]) 78 | } 79 | 80 | function close () { 81 | for (const pair of streams) { 82 | pair[0].destroy() 83 | pair[1].destroy() 84 | } 85 | for (const socket of sockets) { 86 | socket.close() 87 | } 88 | } 89 | 90 | return { sockets, streams, close } 91 | } 92 | 93 | function pipeStreamPairs (streams, messageSize, limit) { 94 | const msg = b4a.alloc(messageSize, 'a') 95 | const proms = [] 96 | 97 | for (const [streamA, streamB] of streams) { 98 | proms.push(write(streamA, limit, msg)) 99 | proms.push(read(streamB, limit)) 100 | } 101 | 102 | return Promise.all(proms) 103 | 104 | function write (s, limit, msg) { 105 | return new Promise((resolve, reject) => { 106 | let written = 0 107 | 108 | s.on('error', reject) 109 | s.on('close', () => { 110 | if (written >= limit) resolve() 111 | }) 112 | 113 | write() 114 | 115 | function write () { 116 | let drained = true 117 | 118 | while (drained && written < limit) { 119 | drained = s.write(msg) 120 | written += msg.length 121 | } 122 | 123 | if (written < limit) s.once('drain', write) 124 | else s.end() 125 | } 126 | }) 127 | } 128 | 129 | function read (s, limit) { 130 | return new Promise((resolve, reject) => { 131 | let read = 0 132 | 133 | s.on('error', reject) 134 | s.on('close', () => { 135 | if (read >= limit) resolve() 136 | }) 137 | 138 | s.on('data', (data) => { 139 | read += data.length 140 | }) 141 | 142 | s.on('end', () => s.end()) 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /test/helpers/proxy.js: -------------------------------------------------------------------------------- 1 | const UDX = require('../..') 2 | 3 | // from udx.h 4 | 5 | const UDX_HEADER_SIZE = 20 6 | const UDX_MAGIC_BYTE = 0xff 7 | const UDX_VERSION = 1 8 | const UDX_HEADER_DATA = 0b00001 9 | const UDX_HEADER_END = 0b00010 10 | const UDX_HEADER_SACK = 0b00100 11 | const UDX_HEADER_MESSAGE = 0b01000 12 | const UDX_HEADER_DESTROY = 0b10000 13 | 14 | module.exports = function proxy ({ from, to, bind } = {}, drop) { 15 | from = toPort(from) 16 | to = toPort(to) 17 | 18 | const u = new UDX() 19 | const socket = u.createSocket() 20 | 21 | socket.bind(bind || 0) 22 | 23 | socket.on('message', function (buf, rinfo) { 24 | const source = { host: rinfo.address, port: rinfo.port, peer: rinfo.port === from ? 'from' : (rinfo.port === to ? 'to' : 'unknown') } 25 | const pkt = parsePacket(buf, source) 26 | const dropping = drop(pkt, source) 27 | const port = rinfo.port === to ? from : to 28 | 29 | if (dropping && dropping.then) dropping.then(fwd).catch(noop) 30 | else fwd(dropping) 31 | 32 | function fwd (dropping) { 33 | if (dropping === true) return 34 | socket.send(buf, port, '127.0.0.1') 35 | } 36 | }) 37 | 38 | return socket 39 | } 40 | 41 | function toPort (n) { 42 | if (typeof n === 'number') return n 43 | if (n && n.address) return n.address().port 44 | throw new Error('Pass a port or socket') 45 | } 46 | 47 | function echo (s) { 48 | return s 49 | } 50 | 51 | function prettyPrint (pkt, { peer }, opts) { 52 | const style = (opts && opts.stylize) || echo 53 | 54 | let s = '' 55 | if (peer === 'from') s += 'from ' + style('-->', 'symbol') + ' to' 56 | else if (peer === 'to') s += 'from ' + style('<--', 'date') + ' to' 57 | else s += 'unknown' 58 | 59 | s += ': ' 60 | 61 | if (pkt.protocol !== 'udx') { 62 | s += style('unknown', 'symbol') + ' data=' + opts.stylize(pkt.data) 63 | return s 64 | } 65 | 66 | const flags = [] 67 | 68 | if (pkt.isData) flags.push(style('data', 'special')) 69 | if (pkt.isEnd) flags.push(style('end', 'special')) 70 | if (pkt.isSack) flags.push(style('sack', 'special')) 71 | if (pkt.isMessage) flags.push(style('message', 'special')) 72 | if (pkt.isDestroy) flags.push(style('destroy', 'special')) 73 | 74 | if (!flags.length) flags.push(style('state', 'special')) 75 | 76 | s += flags.join('+') + ' ' 77 | s += 'stream=' + opts.stylize(pkt.stream, opts) + ' ' 78 | s += 'recv=' + opts.stylize(pkt.recv, opts) + ' ' 79 | s += 'seq=' + opts.stylize(pkt.seq, opts) + ' ' 80 | s += 'ack=' + opts.stylize(pkt.ack, opts) + ' ' 81 | 82 | if (pkt.additionalHeader.byteLength) s += 'additional = ' + opts.stylize(pkt.additionalHeader, opts) + ' ' 83 | if (pkt.data.byteLength) s += 'data=' + opts.stylize(pkt.data, opts) 84 | 85 | s = s.trim() 86 | 87 | return s 88 | } 89 | 90 | function parsePacket (buf, source) { 91 | if (buf.byteLength < UDX_HEADER_SIZE || buf[0] !== UDX_MAGIC_BYTE || buf[1] !== UDX_VERSION) return { protocol: 'unknown', buffer: buf } 92 | 93 | const type = buf[2] 94 | const dataOffset = buf[3] 95 | 96 | const inspect = Symbol.for('nodejs.util.inspect.custom') 97 | 98 | return { 99 | protocol: 'udx', 100 | version: buf[1], 101 | isData: !!(type & UDX_HEADER_DATA), 102 | isEnd: !!(type & UDX_HEADER_END), 103 | isSack: !!(type & UDX_HEADER_SACK), 104 | isMessage: !!(type & UDX_HEADER_MESSAGE), 105 | isDestroy: !!(type & UDX_HEADER_DESTROY), 106 | stream: buf.readUInt32LE(4), 107 | recv: buf.readUInt32LE(8), 108 | seq: buf.readUInt32LE(12), 109 | ack: buf.readUInt32LE(16), 110 | additionalHeader: buf.subarray(UDX_HEADER_SIZE, UDX_HEADER_SIZE + dataOffset), 111 | data: buf.subarray(UDX_HEADER_SIZE + dataOffset), 112 | [inspect] (depth, opts) { 113 | return prettyPrint(this, source, opts) 114 | } 115 | } 116 | } 117 | 118 | function noop () {} 119 | -------------------------------------------------------------------------------- /test/slow/stream-big-writes.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const b4a = require('b4a') 3 | const isCI = require('is-ci') 4 | const { makeTwoStreams } = require('../helpers') 5 | 6 | writeALot(1) 7 | 8 | writeALot(1024) 9 | 10 | writeALot(1024 * 1024) 11 | 12 | writeALot(1024 * 1024 * 1024) 13 | 14 | writeALot(5 * 1024 * 1024 * 1024) 15 | 16 | function writeALot (send) { 17 | test('write as fast as possible (' + fmt(send) + ')', { skip: isCI, timeout: 5 * 60 * 1000 }, async function (t) { 18 | t.timeout(10 * 60 * 1000) 19 | t.plan(5) 20 | 21 | const [a, b] = makeTwoStreams(t) 22 | 23 | const then = Date.now() 24 | 25 | let chunks = 0 26 | let recvBytes = 0 27 | let sentBytes = 0 28 | 29 | const buf = b4a.alloc(Math.min(send, 65536)) 30 | 31 | a.setInteractive(false) 32 | 33 | a.on('data', function (data) { 34 | chunks++ 35 | recvBytes += data.byteLength 36 | }) 37 | 38 | a.on('end', function () { 39 | const delta = Date.now() - then 40 | const perSec = Math.floor(sentBytes / delta * 1000) 41 | 42 | t.is(recvBytes, sentBytes, 'sent and recv ' + fmt(send)) 43 | t.pass('total time was ' + delta + ' ms') 44 | t.pass('send rate was ' + fmt(perSec) + '/s (' + chunks + ' chunk[s])') 45 | 46 | a.end() 47 | }) 48 | 49 | write() 50 | b.on('drain', write) 51 | 52 | a.on('close', function () { 53 | t.pass('a closed') 54 | }) 55 | 56 | b.on('close', function () { 57 | t.pass('b closed') 58 | }) 59 | 60 | function write () { 61 | while (sentBytes < send) { 62 | sentBytes += buf.byteLength 63 | if (!b.write(buf)) break 64 | } 65 | 66 | if (sentBytes >= send) b.end() 67 | } 68 | }) 69 | 70 | function fmt (bytes) { 71 | if (bytes >= 1024 * 1024 * 1024) return (bytes / 1024 / 1024 / 1024).toFixed(1).replace(/\.0$/, '') + ' GB' 72 | if (bytes >= 1024 * 1024) return (bytes / 1024 / 1024).toFixed(1).replace(/\.0$/, '') + ' MB' 73 | if (bytes >= 1024) return (bytes / 1024).toFixed(1).replace(/\.0$/, '') + ' KB' 74 | return bytes + ' B' 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /test/slow/timeouts.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const b4a = require('b4a') 3 | const UDX = require('../../') 4 | 5 | test('default firewall - same socket', async function (t) { 6 | t.plan(1) 7 | 8 | const u = new UDX() 9 | 10 | const socket = u.createSocket() 11 | socket.bind(0) 12 | 13 | const a = u.createStream(1) 14 | const b = u.createStream(2) 15 | 16 | b.on('data', function (data) { 17 | t.fail('default firewall should not allow to receive data') 18 | }) 19 | 20 | a.on('error', function (error) { 21 | t.is(error.code, 'ETIMEDOUT') 22 | 23 | socket.close() 24 | }) 25 | 26 | a.connect(socket, 2, socket.address().port) 27 | a.write(b4a.from('hello')) 28 | }) 29 | -------------------------------------------------------------------------------- /test/socket.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const b4a = require('b4a') 3 | const UDX = require('../') 4 | const { uncaught, createSocket } = require('./helpers') 5 | 6 | test('can bind and close', async function (t) { 7 | const u = new UDX() 8 | const s = createSocket(t, u) 9 | 10 | s.bind(0, '127.0.0.1') 11 | await s.close() 12 | 13 | t.pass() 14 | }) 15 | 16 | test('can bind to ipv6 and close', async function (t) { 17 | const u = new UDX() 18 | const s = createSocket(t, u) 19 | 20 | s.bind(0, '::1') 21 | await s.close() 22 | 23 | t.pass() 24 | }) 25 | 26 | test('bind is effectively sync', async function (t) { 27 | const u = new UDX() 28 | 29 | const a = createSocket(t, u) 30 | const b = createSocket(t, u) 31 | 32 | a.bind(0, '127.0.0.1') 33 | 34 | t.ok(a.address().port, 'has bound') 35 | t.exception(() => b.bind(a.address().port, '127.0.0.1')) 36 | 37 | await a.close() 38 | await b.close() 39 | }) 40 | 41 | test('simple message', async function (t) { 42 | t.plan(4) 43 | 44 | const u = new UDX() 45 | const a = createSocket(t, u) 46 | 47 | a.on('message', function (message, { host, family, port }) { 48 | t.alike(message, b4a.from('hello')) 49 | t.is(host, '127.0.0.1') 50 | t.is(family, 4) 51 | t.is(port, a.address().port) 52 | a.close() 53 | }) 54 | 55 | a.bind(0, '127.0.0.1') 56 | await a.send(b4a.from('hello'), a.address().port) 57 | }) 58 | 59 | test('simple message ipv6', async function (t) { 60 | t.plan(4) 61 | 62 | const u = new UDX() 63 | const a = createSocket(t, u) 64 | 65 | a.on('message', function (message, { host, family, port }) { 66 | t.alike(message, b4a.from('hello')) 67 | t.is(host, '::1') 68 | t.is(family, 6) 69 | t.is(port, a.address().port) 70 | a.close() 71 | }) 72 | 73 | a.bind(0, '::1') 74 | await a.send(b4a.from('hello'), a.address().port, '::1') 75 | }) 76 | 77 | test('simple multicast message ipv4', async function (t) { 78 | t.plan(3) 79 | const u = new UDX() 80 | const a = createSocket(t, u, { reuseAddress: true }) 81 | 82 | a.on('message', function (message, { host, family, port }) { 83 | t.alike(message, b4a.from('hello')) 84 | t.is(family, 4) 85 | t.is(port, a.address().port) 86 | a.dropMembership('239.1.1.1') 87 | a.close() 88 | }) 89 | 90 | a.bind(0, '0.0.0.0') 91 | a.addMembership('239.1.1.1') 92 | 93 | await (a.send(b4a.from('hello'), a.address().port, '239.1.1.1')) 94 | }) 95 | 96 | test.skip('simple multicast message ipv6', async function (t) { 97 | t.plan(3) 98 | const u = new UDX() 99 | const a = createSocket(t, u, { reuseAddress: true }) 100 | 101 | a.on('message', function (message, { host, family, port }) { 102 | t.alike(message, b4a.from('hello')) 103 | t.is(family, 6) 104 | t.is(port, a.address().port) 105 | a.dropMembership('ff12::f') 106 | a.close() 107 | }) 108 | 109 | a.bind(0, '::') 110 | a.addMembership('ff12::f') 111 | 112 | await (a.send(b4a.from('hello'), a.address().port, 'ff12::f')) 113 | }) 114 | 115 | test('empty message', async function (t) { 116 | t.plan(1) 117 | 118 | const u = new UDX() 119 | const a = createSocket(t, u) 120 | 121 | a.on('message', function (message) { 122 | t.alike(message, b4a.alloc(0)) 123 | a.close() 124 | }) 125 | 126 | a.bind(0, '127.0.0.1') 127 | await a.send(b4a.alloc(0), a.address().port) 128 | }) 129 | 130 | test('handshake', async function (t) { 131 | t.plan(2) 132 | 133 | const u = new UDX() 134 | const a = createSocket(t, u) 135 | const b = createSocket(t, u) 136 | 137 | t.teardown(async () => { 138 | await a.close() 139 | await b.close() 140 | }) 141 | 142 | a.once('message', function (message) { 143 | t.alike(message, b4a.alloc(1)) 144 | }) 145 | 146 | b.once('message', function (message) { 147 | t.alike(message, b4a.alloc(0)) 148 | }) 149 | 150 | a.bind(0, '127.0.0.1') 151 | b.bind(0, '127.0.0.1') 152 | 153 | a.trySend(b4a.alloc(0), b.address().port) 154 | b.trySend(b4a.alloc(1), a.address().port) 155 | }) 156 | 157 | test('echo sockets (250 messages)', async function (t) { 158 | t.plan(3) 159 | 160 | const u = new UDX() 161 | 162 | const a = createSocket(t, u) 163 | const b = createSocket(t, u) 164 | 165 | const send = [] 166 | const recv = [] 167 | let echoed = 0 168 | let flushed = 0 169 | 170 | a.on('message', function (buf, { host, port }) { 171 | echoed++ 172 | a.send(buf, port, host) 173 | }) 174 | 175 | b.on('message', function (buf) { 176 | recv.push(buf) 177 | 178 | if (recv.length === 250) { 179 | t.alike(send, recv) 180 | t.is(echoed, 250) 181 | t.is(flushed, 250) 182 | a.close() 183 | b.close() 184 | } 185 | }) 186 | 187 | a.bind(0, '127.0.0.1') 188 | 189 | while (send.length < 250) { 190 | const buf = b4a.from('a message') 191 | send.push(buf) 192 | b.send(buf, a.address().port).then(function () { 193 | flushed++ 194 | }) 195 | } 196 | }) 197 | 198 | test('close socket while sending', async function (t) { 199 | const u = new UDX() 200 | const a = createSocket(t, u) 201 | 202 | a.bind(0, '127.0.0.1') 203 | const flushed = a.send(b4a.from('hello'), a.address().port) 204 | 205 | a.close() 206 | 207 | t.is(await flushed, false) 208 | }) 209 | 210 | test('close waits for all streams to close', async function (t) { 211 | t.plan(2) 212 | 213 | const u = new UDX() 214 | 215 | // just so we can a udx port, to avoid weird failures 216 | const dummy = createSocket(t, u) 217 | dummy.bind(0, '127.0.0.1') 218 | t.teardown(() => dummy.close()) 219 | 220 | const a = createSocket(t, u) 221 | const s = u.createStream(1) 222 | 223 | s.connect(a, 2, dummy.address().port) 224 | 225 | let aClosed = false 226 | let sClosed = false 227 | 228 | s.on('close', function () { 229 | t.not(aClosed, 'socket waits for streams') 230 | sClosed = true 231 | }) 232 | 233 | a.on('close', function () { 234 | t.ok(sClosed, 'stream was closed before socket') 235 | aClosed = true 236 | }) 237 | 238 | a.close() 239 | 240 | setTimeout(function () { 241 | s.destroy() 242 | }, 100) 243 | }) 244 | 245 | test('open + close a bunch of sockets', async function (t) { 246 | const u = new UDX() 247 | 248 | const l = t.test('linear') 249 | let count = 0 250 | 251 | l.plan(5) 252 | loop() 253 | 254 | async function loop () { 255 | count++ 256 | 257 | const a = createSocket(t, u) 258 | 259 | a.bind(0, '127.0.0.1') 260 | l.pass('opened socket') 261 | await a.close() 262 | 263 | if (count < 5) loop() 264 | } 265 | 266 | await l 267 | 268 | const p = t.test('parallel') 269 | p.plan(5) 270 | 271 | for (let i = 0; i < 5; i++) { 272 | const a = createSocket(t, u) 273 | a.bind(0, '127.0.0.1') 274 | a.close().then(function () { 275 | p.pass('opened and closed socket') 276 | }) 277 | } 278 | 279 | await p 280 | }) 281 | 282 | test('can bind to ipv6 and receive from ipv4', async function (t) { 283 | t.plan(4) 284 | 285 | const u = new UDX() 286 | 287 | const a = createSocket(t, u) 288 | const b = createSocket(t, u) 289 | 290 | a.on('message', async function (message, { host, family, port }) { 291 | t.alike(message, b4a.from('hello')) 292 | t.is(host, '127.0.0.1') 293 | t.is(family, 4) 294 | t.is(port, b.address().port) 295 | a.close() 296 | b.close() 297 | }) 298 | 299 | a.bind(0, '::') 300 | b.bind(0, '127.0.0.1') 301 | 302 | b.send(b4a.from('hello'), a.address().port, '127.0.0.1') 303 | }) 304 | 305 | test('can bind to ipv6 and send to ipv4', async function (t) { 306 | t.plan(4) 307 | 308 | const u = new UDX() 309 | 310 | const a = createSocket(t, u) 311 | const b = createSocket(t, u) 312 | 313 | b.on('message', async function (message, { host, family, port }) { 314 | t.alike(message, b4a.from('hello')) 315 | t.is(host, '127.0.0.1') 316 | t.is(family, 4) 317 | t.is(port, a.address().port) 318 | a.close() 319 | b.close() 320 | }) 321 | 322 | a.bind(0, '::') 323 | b.bind(0, '127.0.0.1') 324 | 325 | a.send(b4a.from('hello'), b.address().port, '127.0.0.1') 326 | }) 327 | 328 | test('can bind to ipv6 only and not receive from ipv4', async function (t) { 329 | t.plan(1) 330 | 331 | const u = new UDX() 332 | 333 | const a = createSocket(t, u, { ipv6Only: true }) 334 | const b = createSocket(t, u) 335 | 336 | a.on('message', async function () { 337 | t.fail('a received message') 338 | }) 339 | 340 | a.bind(0, '::') 341 | b.bind(0, '127.0.0.1') 342 | 343 | b.send(b4a.from('hello'), a.address().port, '127.0.0.1') 344 | 345 | setTimeout(() => { 346 | a.close() 347 | b.close() 348 | 349 | t.pass() 350 | }, 100) 351 | }) 352 | 353 | test('send after close', async function (t) { 354 | const u = new UDX() 355 | 356 | const a = createSocket(t, u) 357 | 358 | a.bind(0, '127.0.0.1') 359 | a.close() 360 | 361 | t.is(await a.send(b4a.from('hello'), a.address().port), false) 362 | }) 363 | 364 | test('try send simple message', async function (t) { 365 | t.plan(4) 366 | 367 | const u = new UDX() 368 | const a = createSocket(t, u) 369 | 370 | a.on('message', function (message, { host, family, port }) { 371 | t.alike(message, b4a.from('hello')) 372 | t.is(host, '127.0.0.1') 373 | t.is(family, 4) 374 | t.is(port, a.address().port) 375 | a.close() 376 | }) 377 | 378 | a.bind(0, '127.0.0.1') 379 | a.trySend(b4a.from('hello'), a.address().port) 380 | }) 381 | 382 | test('try send simple message ipv6', async function (t) { 383 | t.plan(4) 384 | 385 | const u = new UDX() 386 | const a = createSocket(t, u) 387 | 388 | a.on('message', function (message, { host, family, port }) { 389 | t.alike(message, b4a.from('hello')) 390 | t.is(host, '::1') 391 | t.is(family, 6) 392 | t.is(port, a.address().port) 393 | a.close() 394 | }) 395 | 396 | a.bind(0, '::1') 397 | a.trySend(b4a.from('hello'), a.address().port, '::1') 398 | }) 399 | 400 | test('try send empty message', async function (t) { 401 | t.plan(1) 402 | 403 | const u = new UDX() 404 | const a = createSocket(t, u) 405 | 406 | a.on('message', function (message) { 407 | t.alike(message, b4a.alloc(0)) 408 | a.close() 409 | }) 410 | 411 | a.bind(0, '127.0.0.1') 412 | a.trySend(b4a.alloc(0), a.address().port) 413 | }) 414 | 415 | test('close socket while try sending', async function (t) { 416 | t.plan(2) 417 | 418 | const u = new UDX() 419 | const a = createSocket(t, u) 420 | 421 | a.bind(0, '127.0.0.1') 422 | 423 | a.on('message', function (message) { 424 | t.fail('should not receive message') 425 | }) 426 | 427 | a.on('close', function () { 428 | t.pass() 429 | }) 430 | 431 | t.is(a.trySend(b4a.from('hello'), a.address().port), undefined) 432 | 433 | a.close() 434 | }) 435 | 436 | test('try send after close', async function (t) { 437 | t.plan(2) 438 | 439 | const u = new UDX() 440 | 441 | const a = createSocket(t, u) 442 | 443 | a.on('message', function (message) { 444 | t.fail('should not receive message') 445 | }) 446 | 447 | a.on('close', function () { 448 | t.pass() 449 | }) 450 | 451 | a.bind(0, '127.0.0.1') 452 | a.close() 453 | 454 | t.is(a.trySend(b4a.from('hello'), a.address().port), undefined) 455 | }) 456 | 457 | test('connect to invalid host ip', async function (t) { 458 | t.plan(1) 459 | 460 | const u = new UDX() 461 | 462 | const a = createSocket(t, u) 463 | const s = u.createStream(1) 464 | 465 | const invalidHost = '0.-1.0.0' 466 | 467 | try { 468 | s.connect(a, 2, 0, invalidHost) 469 | } catch (error) { 470 | t.is(error.message, `${invalidHost} is not a valid IP address`) 471 | } 472 | 473 | s.destroy() 474 | 475 | await a.close() 476 | }) 477 | 478 | test('bind to invalid host ip', async function (t) { 479 | t.plan(1) 480 | 481 | const u = new UDX() 482 | const a = createSocket(t, u) 483 | 484 | const invalidHost = '0.-1.0.0' 485 | 486 | try { 487 | a.bind(0, invalidHost) 488 | } catch (error) { 489 | t.is(error.message, `${invalidHost} is not a valid IP address`) 490 | } 491 | 492 | await a.close() 493 | }) 494 | 495 | test('send to invalid host ip', async function (t) { 496 | t.plan(1) 497 | 498 | const u = new UDX() 499 | const a = createSocket(t, u) 500 | 501 | a.bind(0, '127.0.0.1') 502 | 503 | const invalidHost = '0.-1.0.0' 504 | 505 | try { 506 | await a.send(b4a.from('hello'), a.address().port, invalidHost) 507 | } catch (error) { 508 | t.is(error.message, `${invalidHost} is not a valid IP address`) 509 | } 510 | 511 | await a.close() 512 | }) 513 | 514 | test('try send to invalid host ip', async function (t) { 515 | t.plan(1) 516 | 517 | const u = new UDX() 518 | const a = createSocket(t, u) 519 | 520 | a.bind(0, '127.0.0.1') 521 | 522 | const invalidHost = '0.-1.0.0' 523 | 524 | try { 525 | a.trySend(b4a.from('hello'), a.address().port, invalidHost) 526 | } catch (error) { 527 | t.is(error.message, `${invalidHost} is not a valid IP address`) 528 | } 529 | 530 | await a.close() 531 | }) 532 | 533 | test('send without bind', async function (t) { 534 | t.plan(1) 535 | 536 | const u = new UDX() 537 | 538 | const a = createSocket(t, u) 539 | const b = createSocket(t, u) 540 | 541 | b.on('message', function (message) { 542 | t.alike(message, b4a.from('hello')) 543 | a.close() 544 | b.close() 545 | }) 546 | 547 | b.bind(0, '127.0.0.1') 548 | await a.send(b4a.from('hello'), b.address().port) 549 | }) 550 | 551 | test('try send without bind', async function (t) { 552 | t.plan(1) 553 | 554 | const u = new UDX() 555 | 556 | const a = createSocket(t, u) 557 | const b = createSocket(t, u) 558 | 559 | b.on('message', function (message) { 560 | t.alike(message, b4a.from('hello')) 561 | a.close() 562 | b.close() 563 | }) 564 | 565 | b.bind(0, '127.0.0.1') 566 | a.trySend(b4a.from('hello'), b.address().port) 567 | }) 568 | 569 | test('throw in message callback', async function (t) { 570 | t.plan(1) 571 | 572 | const u = new UDX() 573 | 574 | const a = createSocket(t, u) 575 | const b = createSocket(t, u) 576 | 577 | a.on('message', function () { 578 | throw new Error('boom') 579 | }) 580 | 581 | a.bind(0, '127.0.0.1') 582 | 583 | b.send(b4a.from('hello'), a.address().port) 584 | 585 | uncaught(async (err) => { 586 | t.is(err.message, 'boom') 587 | 588 | await a.close() 589 | await b.close() 590 | }) 591 | }) 592 | 593 | test('get address without bind', async function (t) { 594 | const u = new UDX() 595 | const a = createSocket(t, u) 596 | t.is(a.address(), null) 597 | await a.close() 598 | }) 599 | 600 | test('bind twice', async function (t) { 601 | t.plan(1) 602 | 603 | const u = new UDX() 604 | const a = createSocket(t, u) 605 | 606 | a.bind(0, '127.0.0.1') 607 | 608 | try { 609 | a.bind(0, '127.0.0.1') 610 | } catch (error) { 611 | t.is(error.message, 'Already bound') 612 | } 613 | 614 | await a.close() 615 | }) 616 | 617 | test('bind while closing', function (t) { 618 | t.plan(1) 619 | 620 | const u = new UDX() 621 | const a = createSocket(t, u) 622 | 623 | a.close() 624 | 625 | try { 626 | a.bind(0, '127.0.0.1') 627 | } catch (error) { 628 | t.is(error.message, 'Socket is closed') 629 | } 630 | }) 631 | 632 | test('different socket binds to same host and port', async function (t) { 633 | t.plan(1) 634 | 635 | const u = new UDX() 636 | const a = createSocket(t, u) 637 | const b = createSocket(t, u) 638 | 639 | a.bind(0, '0.0.0.0') 640 | 641 | try { 642 | b.bind(a.address().port, '0.0.0.0') 643 | } catch { 644 | t.pass() 645 | } 646 | 647 | await a.close() 648 | await b.close() 649 | }) 650 | 651 | test('different socket binds to default host but same port', async function (t) { 652 | t.plan(1) 653 | 654 | const u = new UDX() 655 | const a = createSocket(t, u) 656 | const b = createSocket(t, u) 657 | 658 | a.bind() 659 | 660 | try { 661 | b.bind(a.address().port) 662 | } catch { 663 | t.pass() 664 | } 665 | 666 | await a.close() 667 | await b.close() 668 | }) 669 | 670 | test('close twice', async function (t) { 671 | t.plan(1) 672 | 673 | const u = new UDX() 674 | const a = createSocket(t, u) 675 | 676 | a.bind(0, '127.0.0.1') 677 | 678 | a.on('close', function () { 679 | t.pass() 680 | }) 681 | 682 | a.close() 683 | a.close() 684 | }) 685 | 686 | test('set TTL', async function (t) { 687 | t.plan(2) 688 | 689 | const u = new UDX() 690 | const a = createSocket(t, u) 691 | 692 | a.on('message', function (message) { 693 | t.alike(message, b4a.from('hello')) 694 | a.close() 695 | }) 696 | 697 | try { 698 | a.setTTL(5) 699 | } catch (error) { 700 | t.is(error.message, 'Socket not active') 701 | } 702 | 703 | a.bind(0, '127.0.0.1') 704 | a.setTTL(5) 705 | 706 | await a.send(b4a.from('hello'), a.address().port) 707 | }) 708 | 709 | test('get recv buffer size', async function (t) { 710 | t.plan(2) 711 | 712 | const u = new UDX() 713 | const a = createSocket(t, u) 714 | 715 | try { 716 | a.getRecvBufferSize() 717 | } catch (error) { 718 | t.is(error.message, 'Socket not active') 719 | } 720 | 721 | a.bind(0, '127.0.0.1') 722 | t.ok(a.getRecvBufferSize() > 0) 723 | 724 | await a.close() 725 | }) 726 | 727 | test('set recv buffer size', async function (t) { 728 | t.plan(2) 729 | 730 | const u = new UDX() 731 | const a = createSocket(t, u) 732 | 733 | const NEW_BUFFER_SIZE = 8192 734 | 735 | try { 736 | a.setRecvBufferSize(NEW_BUFFER_SIZE) 737 | } catch (error) { 738 | t.is(error.message, 'Socket not active') 739 | } 740 | 741 | a.bind(0, '127.0.0.1') 742 | a.setRecvBufferSize(NEW_BUFFER_SIZE) 743 | 744 | t.ok(a.getRecvBufferSize() >= NEW_BUFFER_SIZE) 745 | 746 | await a.close() 747 | }) 748 | 749 | test('get send buffer size', async function (t) { 750 | t.plan(2) 751 | 752 | const u = new UDX() 753 | const a = createSocket(t, u) 754 | 755 | try { 756 | a.getSendBufferSize() 757 | } catch (error) { 758 | t.is(error.message, 'Socket not active') 759 | } 760 | 761 | a.bind(0, '127.0.0.1') 762 | t.ok(a.getSendBufferSize() > 0) 763 | 764 | await a.close() 765 | }) 766 | 767 | test('set send buffer size', async function (t) { 768 | t.plan(2) 769 | 770 | const u = new UDX() 771 | const a = createSocket(t, u) 772 | 773 | const NEW_BUFFER_SIZE = 8192 774 | 775 | try { 776 | a.setSendBufferSize(NEW_BUFFER_SIZE) 777 | } catch (error) { 778 | t.is(error.message, 'Socket not active') 779 | } 780 | 781 | a.bind(0, '127.0.0.1') 782 | a.setSendBufferSize(NEW_BUFFER_SIZE) 783 | 784 | t.ok(a.getSendBufferSize() >= NEW_BUFFER_SIZE) 785 | 786 | await a.close() 787 | }) 788 | 789 | test('UDX - socket stats 0 before bind is called', async function (t) { 790 | const a = new UDX() 791 | 792 | const aSocket = a.createSocket() 793 | 794 | t.is(aSocket.bytesTransmitted, 0) 795 | t.is(aSocket.packetsTransmitted, 0) 796 | t.is(aSocket.bytesReceived, 0) 797 | t.is(aSocket.packetsReceived, 0) 798 | }) 799 | -------------------------------------------------------------------------------- /test/stream-framed.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const b4a = require('b4a') 3 | const { makeTwoStreams } = require('./helpers') 4 | 5 | test('framed mode', function (t) { 6 | t.plan(1) 7 | 8 | const [a, b] = makeTwoStreams(t, { framed: true }) 9 | 10 | b.on('data', (buffer) => { 11 | t.alike(buffer, b4a.from([0x2, 0x0, 0x0, 0x4, 0x5])) 12 | 13 | a.destroy() 14 | b.destroy() 15 | }) 16 | 17 | a.write(b4a.from([0x2, 0x0, 0x0])) 18 | a.write(b4a.from([0x4, 0x5])) 19 | }) 20 | 21 | test('framed mode, large message', function (t) { 22 | t.plan(1) 23 | 24 | const [a, b] = makeTwoStreams(t, { framed: true }) 25 | 26 | const buf = b4a.alloc(3 + 1024 * 4096 /* 4 MiB */) 27 | 28 | buf[2] = 0x40 29 | 30 | const recv = [] 31 | 32 | b 33 | .on('data', (buffer) => { 34 | recv.push(buffer) 35 | }) 36 | .on('end', () => { 37 | t.alike(b4a.concat(recv), buf) 38 | 39 | a.destroy() 40 | b.destroy() 41 | }) 42 | 43 | a.end(buf) 44 | }) 45 | 46 | test('framed mode, several frames', function (t) { 47 | t.plan(1) 48 | 49 | const [a, b] = makeTwoStreams(t, { framed: true }) 50 | 51 | const buf = b4a.from([0x2, 0x0, 0x0, 0x4, 0x5]) 52 | 53 | const recv = [] 54 | 55 | b 56 | .on('data', (buffer) => { 57 | recv.push(buffer) 58 | }) 59 | .on('end', () => { 60 | t.alike(b4a.concat(recv), b4a.concat([buf, buf, buf])) 61 | 62 | a.destroy() 63 | b.destroy() 64 | }) 65 | 66 | a.write(buf) 67 | a.write(buf) 68 | a.end(buf) 69 | }) 70 | 71 | test('framed mode, invalid frame', function (t) { 72 | t.plan(1) 73 | 74 | const [a, b] = makeTwoStreams(t, { framed: true }) 75 | 76 | const buf = b4a.from([0x1 /* too short, leftover data */, 0x0, 0x0, 0x4, 0x5]) 77 | 78 | b 79 | .on('data', (buffer) => { 80 | t.alike(buffer, buf) 81 | 82 | a.destroy() 83 | b.destroy() 84 | }) 85 | 86 | a.write(buf) 87 | }) 88 | -------------------------------------------------------------------------------- /test/stream-parallel.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const { makePairs, pipeStreamPairs } = require('./helpers') 3 | 4 | test('16 parallel streams on 1 socket', async function (t) { 5 | t.timeout(90000) 6 | const { streams, close } = makePairs(16, 'single') 7 | t.teardown(close) 8 | t.plan(1) 9 | const messageSize = 1024 * 64 10 | const limit = 1024 * 512 11 | 12 | await t.execution(pipeStreamPairs(streams, messageSize, limit)) 13 | }) 14 | 15 | test('16 parallel streams on 16 sockets', async function (t) { 16 | t.timeout(90000) 17 | const { streams, close } = makePairs(16, 'multi') 18 | t.teardown(close) 19 | t.plan(1) 20 | const messageSize = 1024 * 64 21 | const limit = 1024 * 512 22 | 23 | await t.execution(pipeStreamPairs(streams, messageSize, limit)) 24 | }) 25 | -------------------------------------------------------------------------------- /test/stream-relay.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const b4a = require('b4a') 3 | const { makeTwoStreams } = require('./helpers') 4 | 5 | test('relay', function (t) { 6 | t.plan(1) 7 | 8 | const [a, b] = makeTwoStreams(t) 9 | const [c, d] = makeTwoStreams(t) 10 | 11 | c.relayTo(b) 12 | b.relayTo(c) 13 | 14 | a.on('data', function (data) { 15 | t.alike(data, b4a.from('hello world')) 16 | 17 | a.destroy() 18 | b.destroy() 19 | c.destroy() 20 | d.destroy() 21 | }) 22 | 23 | d.write('hello world') 24 | }) 25 | 26 | test('relay, destroy immediately', function (t) { 27 | const [a, b] = makeTwoStreams(t) 28 | const [c, d] = makeTwoStreams(t) 29 | 30 | c.relayTo(b) 31 | b.relayTo(c) 32 | 33 | a.destroy() 34 | b.destroy() 35 | c.destroy() 36 | d.destroy() 37 | 38 | t.pass() 39 | }) 40 | 41 | test('relay, change remote', function (t) { 42 | t.plan(2) 43 | 44 | const [a, b] = makeTwoStreams(t) 45 | const [c, d] = makeTwoStreams(t) 46 | 47 | c.relayTo(b) 48 | b.relayTo(c) 49 | 50 | a.once('data', async function (data) { 51 | t.alike(data, b4a.from('hello world')) 52 | 53 | await a.changeRemote(a.socket, d.id, d.socket.address().port) 54 | await d.changeRemote(d.socket, a.id, a.socket.address().port) 55 | 56 | b.destroy() 57 | c.destroy() 58 | 59 | a.once('data', function (data) { 60 | t.alike(data, b4a.from('remote changed')) 61 | 62 | a.destroy() 63 | d.destroy() 64 | }) 65 | 66 | d.write('remote changed') 67 | }) 68 | 69 | d.write('hello world') 70 | }) 71 | 72 | test('relay, change remote and destroy stream', function (t) { 73 | t.plan(2) 74 | 75 | const [a, b] = makeTwoStreams(t) 76 | const [c, d] = makeTwoStreams(t) 77 | 78 | c.relayTo(b) 79 | b.relayTo(c) 80 | 81 | a.once('data', async function (data) { 82 | t.alike(data, b4a.from('hello world')) 83 | 84 | const promises = [ 85 | a.changeRemote(a.socket, d.id, d.socket.address().port), 86 | d.changeRemote(d.socket, a.id, a.socket.address().port) 87 | ] 88 | 89 | a.destroy() 90 | b.destroy() 91 | c.destroy() 92 | d.destroy() 93 | 94 | await Promise.allSettled(promises) 95 | 96 | t.pass() 97 | }) 98 | 99 | d.write('hello world') 100 | }) 101 | 102 | test('relay, throw if stream is closed', function (t) { 103 | t.plan(1) 104 | 105 | const [a, b] = makeTwoStreams(t) 106 | 107 | a 108 | .on('close', () => { 109 | try { 110 | b.relayTo(a) 111 | t.fail('should fail') 112 | } catch (err) { 113 | t.ok(err) 114 | b.destroy() 115 | } 116 | }) 117 | .destroy() 118 | }) 119 | -------------------------------------------------------------------------------- /test/stream.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const b4a = require('b4a') 3 | const { Readable } = require('streamx') 4 | const proxy = require('./helpers/proxy') 5 | const UDX = require('../') 6 | const { makeTwoStreams, uncaught, createSocket } = require('./helpers') 7 | 8 | test('tiny echo stream', async function (t) { 9 | t.plan(8) 10 | 11 | const [a, b] = makeTwoStreams(t) 12 | 13 | a.on('data', function (data) { 14 | t.alike(data, b4a.from('echo: hello world'), 'a received echoed data') 15 | }) 16 | 17 | a.on('end', function () { 18 | t.pass('a ended') 19 | }) 20 | 21 | a.on('finish', function () { 22 | t.pass('a finished') 23 | }) 24 | 25 | a.on('close', function () { 26 | t.pass('a closed') 27 | }) 28 | 29 | b.on('data', function (data) { 30 | t.alike(data, b4a.from('hello world'), 'b received data') 31 | b.write(b4a.concat([b4a.from('echo: '), data])) 32 | }) 33 | 34 | b.on('end', function () { 35 | t.pass('b ended') 36 | b.end() 37 | }) 38 | 39 | b.on('finish', function () { 40 | t.pass('b finished') 41 | }) 42 | 43 | b.on('close', function () { 44 | t.pass('b closed') 45 | }) 46 | 47 | a.write(b4a.from('hello world')) 48 | a.end() 49 | }) 50 | 51 | test('stream flush', async function (t) { 52 | const [a, b] = makeTwoStreams(t) 53 | 54 | a.write('hello') 55 | a.write(' ') 56 | a.write('world') 57 | 58 | const all = [] 59 | b.on('data', data => { 60 | all.push(data) 61 | }) 62 | 63 | await a.flush() 64 | 65 | const recv = b4a.concat(all) 66 | t.alike(recv, b4a.from('hello world')) 67 | 68 | a.end() 69 | b.end() 70 | }) 71 | 72 | test('end immediately', async function (t) { 73 | t.plan(6) 74 | 75 | const [a, b] = makeTwoStreams(t) 76 | 77 | a.on('data', function () { 78 | t.fail('should not send data') 79 | }) 80 | 81 | a.on('end', function () { 82 | t.pass('a ended') 83 | }) 84 | 85 | a.on('finish', function () { 86 | t.pass('a finished') 87 | }) 88 | 89 | a.on('close', function () { 90 | t.pass('a closed') 91 | }) 92 | 93 | b.on('data', function (data) { 94 | t.fail('should not send data') 95 | }) 96 | 97 | b.on('end', function () { 98 | t.pass('b ended') 99 | b.end() 100 | }) 101 | 102 | b.on('finish', function () { 103 | t.pass('b finished') 104 | }) 105 | 106 | b.on('close', function () { 107 | t.pass('b closed') 108 | }) 109 | 110 | a.end() 111 | }) 112 | 113 | test('only one side writes', async function (t) { 114 | t.plan(7) 115 | 116 | const [a, b] = makeTwoStreams(t) 117 | 118 | a.on('data', function () { 119 | t.fail('should not send data') 120 | }) 121 | 122 | a.on('end', function () { 123 | t.pass('a ended') 124 | }) 125 | 126 | a.on('finish', function () { 127 | t.pass('a finished') 128 | }) 129 | 130 | a.on('close', function () { 131 | t.pass('a closed') 132 | }) 133 | 134 | b.on('data', function (data) { 135 | t.alike(data, b4a.from('hello world'), 'b received data') 136 | }) 137 | 138 | b.on('end', function () { 139 | t.pass('b ended') 140 | b.end() 141 | }) 142 | 143 | b.on('finish', function () { 144 | t.pass('b finished') 145 | }) 146 | 147 | b.on('close', function () { 148 | t.pass('b closed') 149 | }) 150 | 151 | a.write(b4a.from('hello world')) 152 | a.end() 153 | }) 154 | 155 | test('emit connect', async function (t) { 156 | const udx = new UDX() 157 | 158 | const socket = createSocket(t, udx) 159 | socket.bind(0, '127.0.0.1') 160 | 161 | const a = udx.createStream(1) 162 | 163 | a 164 | .on('connect', function () { 165 | t.pass() 166 | a.destroy() 167 | socket.close() 168 | }) 169 | .connect(socket, 2, socket.address().port) 170 | }) 171 | 172 | test('unordered messages', async function (t) { 173 | t.plan(2) 174 | 175 | const [a, b] = makeTwoStreams(t) 176 | const expected = [] 177 | 178 | b.on('message', function (buf) { 179 | b.send(b4a.from('echo: ' + buf.toString())) 180 | }) 181 | 182 | a.on('error', function () { 183 | t.pass('a destroyed') 184 | }) 185 | 186 | a.on('message', function (buf) { 187 | expected.push(buf.toString()) 188 | 189 | if (expected.length === 3) { 190 | t.alike(expected.sort(), [ 191 | 'echo: a', 192 | 'echo: bc', 193 | 'echo: d' 194 | ]) 195 | 196 | // TODO: .end() here triggers a bug, investigate 197 | b.destroy() 198 | } 199 | }) 200 | 201 | a.send(b4a.from('a')) 202 | a.send(b4a.from('bc')) 203 | a.send(b4a.from('d')) 204 | }) 205 | 206 | test('try send unordered messages', async function (t) { 207 | t.plan(2) 208 | 209 | const [a, b] = makeTwoStreams(t) 210 | const expected = [] 211 | 212 | b.on('message', function (buf) { 213 | b.trySend(b4a.from('echo: ' + buf.toString())) 214 | }) 215 | 216 | a.on('error', function () { 217 | t.pass('a destroyed') 218 | }) 219 | 220 | a.on('message', function (buf) { 221 | expected.push(buf.toString()) 222 | 223 | if (expected.length === 3) { 224 | t.alike(expected.sort(), [ 225 | 'echo: a', 226 | 'echo: bc', 227 | 'echo: d' 228 | ]) 229 | 230 | // TODO: .end() here triggers a bug, investigate 231 | b.destroy() 232 | } 233 | }) 234 | 235 | a.trySend(b4a.from('a')) 236 | a.trySend(b4a.from('bc')) 237 | a.trySend(b4a.from('d')) 238 | }) 239 | 240 | test('ipv6 streams', async function (t) { 241 | t.plan(1) 242 | 243 | const u = new UDX() 244 | 245 | const aSocket = u.createSocket() 246 | aSocket.bind(0, '::1') 247 | t.teardown(() => aSocket.close()) 248 | 249 | const bSocket = u.createSocket() 250 | bSocket.bind(0, '::1') 251 | t.teardown(() => bSocket.close()) 252 | 253 | const a = u.createStream(1) 254 | const b = u.createStream(2) 255 | 256 | a.connect(aSocket, 2, bSocket.address().port, '::1') 257 | b.connect(bSocket, 1, aSocket.address().port, '::1') 258 | 259 | a.on('data', function (data) { 260 | t.alike(data, b4a.from('hello world')) 261 | a.end() 262 | }) 263 | 264 | b.end('hello world') 265 | }) 266 | 267 | test('several streams on same socket', async function (t) { 268 | const u = new UDX() 269 | 270 | const socket = u.createSocket() 271 | socket.bind(0, '127.0.0.1') 272 | 273 | for (let i = 0; i < 10; i++) { 274 | const stream = u.createStream(i) 275 | stream.connect(socket, i, socket.address().port) 276 | 277 | t.teardown(() => stream.destroy()) 278 | } 279 | 280 | t.teardown(() => socket.close()) 281 | t.pass('halts') 282 | }) 283 | 284 | test('destroy unconnected stream', async function (t) { 285 | t.plan(1) 286 | 287 | const u = new UDX() 288 | 289 | const stream = u.createStream(1) 290 | 291 | stream.on('close', function () { 292 | t.pass('closed') 293 | }) 294 | 295 | stream.destroy() 296 | }) 297 | 298 | test('preconnect flow', async function (t) { 299 | t.plan(9) 300 | 301 | const u = new UDX() 302 | 303 | const socket = u.createSocket() 304 | socket.bind(0, '127.0.0.1') 305 | 306 | let once = true 307 | 308 | const a = u.createStream(1, { 309 | firewall (sock, port, host, family) { 310 | t.ok(once) 311 | t.is(sock, socket) 312 | t.is(port, socket.address().port) 313 | t.is(host, '127.0.0.1') 314 | t.is(family, 4) 315 | once = false 316 | 317 | return false 318 | } 319 | }) 320 | 321 | a.on('data', function (data) { 322 | t.is(data.toString(), 'hello', 'can receive data preconnect') 323 | 324 | a.connect(socket, 2, socket.address().port) 325 | a.end() 326 | }) 327 | 328 | const b = u.createStream(2) 329 | 330 | b.connect(socket, 1, socket.address().port) 331 | b.write(b4a.from('hello')) 332 | b.end() 333 | 334 | let closed = 0 335 | 336 | b.resume() 337 | b.on('close', function () { 338 | t.pass('b closed') 339 | if (++closed === 2) socket.close() 340 | }) 341 | 342 | a.on('close', function () { 343 | t.pass('a closed') 344 | if (++closed === 2) socket.close() 345 | }) 346 | 347 | socket.on('close', function () { 348 | t.pass('socket closed') 349 | }) 350 | }) 351 | 352 | test('destroy streams and close socket in callback', async function (t) { 353 | t.plan(1) 354 | 355 | const u = new UDX() 356 | 357 | const socket = u.createSocket() 358 | socket.bind(0, '127.0.0.1') 359 | 360 | const a = u.createStream(1) 361 | const b = u.createStream(2) 362 | 363 | a.connect(socket, 2, socket.address().port) 364 | b.connect(socket, 1, socket.address().port) 365 | 366 | a.on('data', async function (data) { 367 | a.destroy() 368 | b.destroy() 369 | 370 | await socket.close() 371 | 372 | t.pass('closed') 373 | }) 374 | 375 | b.write(b4a.from('hello')) 376 | }) 377 | 378 | test('write empty buffer', async function (t) { 379 | t.plan(2) 380 | 381 | const [a, b] = makeTwoStreams(t) 382 | 383 | a 384 | .on('close', function () { 385 | t.pass('a closed') 386 | }) 387 | .end() 388 | 389 | b 390 | .on('close', function () { 391 | t.pass('b closed') 392 | }) 393 | .end(b4a.alloc(0)) 394 | }) 395 | 396 | test('out of order packets', async function (t) { 397 | t.plan(3) 398 | 399 | const u = new UDX() 400 | 401 | const a = u.createSocket() 402 | const b = u.createSocket() 403 | 404 | a.bind(0, '127.0.0.1') 405 | b.bind(0, '127.0.0.1') 406 | 407 | const count = 1000 408 | const expected = Array(count).fill(0).map((_, i) => i.toString()).join('') 409 | let received = '' 410 | 411 | const p = await proxy({ from: a, to: b }, async function (pkt) { 412 | // Add a random delay to every packet 413 | await new Promise((resolve) => 414 | setTimeout(resolve, Math.random() * 1000 | 0) 415 | ) 416 | 417 | return false 418 | }) 419 | 420 | const aStream = u.createStream(1) 421 | const bStream = u.createStream(2) 422 | 423 | aStream.connect(a, 2, p.address().port) 424 | bStream.connect(b, 1, p.address().port) 425 | 426 | for (let i = 0; i < count; i++) { 427 | aStream.write(b4a.from(i.toString())) 428 | } 429 | 430 | bStream.on('data', function (s) { 431 | received = received + s.toString() 432 | 433 | if (received.length === expected.length) { 434 | t.alike(received, expected, 'received in order') 435 | 436 | p.close() 437 | aStream.destroy() 438 | bStream.destroy() 439 | } 440 | }) 441 | 442 | aStream.on('close', function () { 443 | t.pass('a stream closed') 444 | b.close() 445 | }) 446 | 447 | bStream.on('close', function () { 448 | t.pass('b stream closed') 449 | a.close() 450 | }) 451 | }) 452 | 453 | test('out of order reads but can destroy (memleak test)', async function (t) { 454 | t.plan(3) 455 | 456 | const u = new UDX() 457 | 458 | const a = u.createSocket() 459 | const b = u.createSocket() 460 | 461 | a.bind(0, '127.0.0.1') 462 | b.bind(0, '127.0.0.1') 463 | 464 | let processed = 0 465 | 466 | const p = await proxy({ from: a, to: b }, function (pkt) { 467 | if (pkt.data.toString().startsWith('a') && processed > 0) { 468 | // destroy with out or order packets delivered 469 | t.pass('close while streams have out of order state') 470 | p.close() 471 | aStream.destroy() 472 | bStream.destroy() 473 | return true 474 | } 475 | 476 | return processed++ === 0 // drop first packet 477 | }) 478 | 479 | const aStream = u.createStream(1) 480 | const bStream = u.createStream(2) 481 | 482 | aStream.connect(a, 2, p.address().port) 483 | bStream.connect(b, 1, p.address().port) 484 | 485 | aStream.write(b4a.from(Array(1200).fill('a').join(''))) 486 | aStream.write(b4a.from(Array(1000).fill('b').join(''))) 487 | 488 | aStream.on('close', function () { 489 | t.pass('a stream closed') 490 | b.close() 491 | }) 492 | 493 | bStream.on('close', function () { 494 | t.pass('b stream closed') 495 | a.close() 496 | }) 497 | }) 498 | 499 | test('close socket on stream close', async function (t) { 500 | t.plan(2) 501 | 502 | const u = new UDX() 503 | 504 | const aSocket = u.createSocket() 505 | aSocket.bind(0, '127.0.0.1') 506 | 507 | const bSocket = u.createSocket() 508 | bSocket.bind(0, '127.0.0.1') 509 | 510 | const a = u.createStream(1) 511 | const b = u.createStream(2) 512 | 513 | a.connect(aSocket, 2, bSocket.address().port) 514 | b.connect(bSocket, 1, aSocket.address().port) 515 | 516 | a 517 | .on('close', async function () { 518 | await aSocket.close() 519 | t.pass('a closed') 520 | }) 521 | 522 | b 523 | .on('end', function () { 524 | b.end() 525 | }) 526 | .on('close', async function () { 527 | await bSocket.close() 528 | t.pass('b closed') 529 | }) 530 | 531 | a.resume() 532 | b.resume() 533 | 534 | a.end() 535 | }) 536 | 537 | test('write string', async function (t) { 538 | t.plan(3) 539 | 540 | const [a, b] = makeTwoStreams(t) 541 | 542 | a 543 | .on('data', function (data) { 544 | t.alike(data, b4a.from('hello world')) 545 | }) 546 | .on('close', function () { 547 | t.pass('a closed') 548 | }) 549 | .end() 550 | 551 | b 552 | .on('close', function () { 553 | t.pass('b closed') 554 | }) 555 | .end('hello world') 556 | }) 557 | 558 | test('destroy before fully connected', async function (t) { 559 | t.plan(2) 560 | 561 | const u = new UDX() 562 | 563 | const socket = u.createSocket() 564 | socket.bind(0, '127.0.0.1') 565 | 566 | const a = u.createStream(1) 567 | const b = u.createStream(2, { 568 | firewall () { 569 | return false // accept packets from a 570 | } 571 | }) 572 | 573 | a.connect(socket, 2, socket.address().port) 574 | a.destroy() 575 | 576 | b 577 | .on('error', function (err) { 578 | t.is(err.code, 'ECONNRESET') 579 | }) 580 | .on('close', async function () { 581 | t.pass('b closed') 582 | await socket.close() 583 | }) 584 | 585 | setTimeout(function () { 586 | b.connect(socket, 1, socket.address().port) 587 | b.destroy() 588 | }, 100) // wait for destroy to be processed 589 | }) 590 | 591 | test('throw in data callback', async function (t) { 592 | t.plan(1) 593 | 594 | const u = new UDX() 595 | 596 | const socket = u.createSocket() 597 | socket.bind(0, '127.0.0.1') 598 | 599 | const a = u.createStream(1) 600 | const b = u.createStream(2) 601 | 602 | a.connect(socket, 2, socket.address().port) 603 | b.connect(socket, 1, socket.address().port) 604 | 605 | a.on('data', function () { 606 | throw new Error('boom') 607 | }) 608 | 609 | b.end(b4a.from('hello')) 610 | 611 | uncaught((err) => { 612 | t.is(err.message, 'boom') 613 | 614 | a.destroy() 615 | b.destroy() 616 | 617 | socket.close() 618 | }) 619 | }) 620 | 621 | test('throw in message callback', async function (t) { 622 | t.plan(1) 623 | 624 | const u = new UDX() 625 | 626 | const socket = u.createSocket() 627 | socket.bind(0, '127.0.0.1') 628 | 629 | const a = u.createStream(1) 630 | const b = u.createStream(2) 631 | 632 | a.connect(socket, 2, socket.address().port) 633 | b.connect(socket, 1, socket.address().port) 634 | 635 | a.on('message', function () { 636 | throw new Error('boom') 637 | }) 638 | 639 | b.send(b4a.from('hello')) 640 | 641 | uncaught((err) => { 642 | t.is(err.message, 'boom') 643 | 644 | a.destroy() 645 | b.destroy() 646 | 647 | socket.close() 648 | }) 649 | }) 650 | 651 | test('seq and ack wraparound', async function (t) { 652 | t.plan(1) 653 | 654 | const u = new UDX() 655 | 656 | let received = '' 657 | // enough data to fill 10 packets 658 | const expected = Array(1500 * 10).join('a') 659 | 660 | const socket = u.createSocket() 661 | socket.bind(0, '127.0.0.1') 662 | 663 | const a = u.createStream(1, { seq: 2 ** 32 - 5 }) 664 | t.teardown(() => a.destroy()) 665 | 666 | const b = u.createStream(2) 667 | t.teardown(() => b.destroy()) 668 | 669 | t.teardown(() => socket.close()) 670 | 671 | a.connect(socket, 2, socket.address().port) 672 | b.connect(socket, 1, socket.address().port, { ack: 2 ** 32 - 5 }) 673 | 674 | b.on('data', function (i) { 675 | received = received + i.toString() 676 | if (expected.length === received.length) { 677 | t.alike(expected, received) 678 | } 679 | }) 680 | 681 | a.write(expected) 682 | }) 683 | 684 | test('busy and idle events', async function (t) { 685 | t.plan(10) 686 | 687 | const udx = new UDX() 688 | 689 | const socket = createSocket(t, udx) 690 | socket.bind(0, '127.0.0.1') 691 | 692 | let idle = false 693 | let busy = false 694 | 695 | socket 696 | .on('idle', function () { 697 | idle = true 698 | busy = false 699 | 700 | socket.close() 701 | }) 702 | .on('busy', function () { 703 | busy = true 704 | idle = false 705 | }) 706 | 707 | const stream = udx.createStream(1) 708 | 709 | t.absent(idle) 710 | t.absent(busy) 711 | 712 | stream 713 | .on('connect', function () { 714 | t.absent(idle) 715 | t.ok(busy) 716 | 717 | t.is(idle, socket.idle) 718 | t.is(busy, socket.busy) 719 | 720 | stream.destroy() 721 | }) 722 | .on('close', function () { 723 | t.ok(idle) 724 | t.absent(busy) 725 | 726 | t.is(idle, socket.idle) 727 | t.is(busy, socket.busy) 728 | }) 729 | .connect(socket, 2, socket.address().port) 730 | }) 731 | 732 | test('no idle after close', async function (t) { 733 | t.plan(1) 734 | 735 | const udx = new UDX() 736 | 737 | const socket = createSocket(t, udx) 738 | socket.bind(0, '127.0.0.1') 739 | 740 | const stream = udx.createStream(1) 741 | 742 | stream 743 | .on('connect', function () { 744 | stream.destroy() 745 | 746 | socket 747 | .on('idle', function () { 748 | t.fail('idle event after close') 749 | }) 750 | .on('close', function () { 751 | t.pass('socket closed') 752 | }) 753 | .close() 754 | }) 755 | .connect(socket, 2, socket.address().port) 756 | }) 757 | 758 | test('localHost, localFamily and localPort', async function (t) { 759 | t.plan(6) 760 | 761 | const udx = new UDX() 762 | 763 | const socket = createSocket(t, udx) 764 | socket.bind(0, '127.0.0.1') 765 | 766 | const stream = udx.createStream(1) 767 | 768 | t.is(stream.localHost, null) 769 | t.is(stream.localFamily, 0) 770 | t.is(stream.localPort, 0) 771 | 772 | stream.on('connect', function () { 773 | t.is(stream.localHost, '127.0.0.1') 774 | t.is(stream.localFamily, 4) 775 | t.is(typeof stream.localPort, 'number') 776 | 777 | stream.destroy() 778 | socket.close() 779 | }) 780 | 781 | stream.connect(socket, 2, socket.address().port, '127.0.0.1') 782 | }) 783 | 784 | test('write to unconnected stream', async function (t) { 785 | t.plan(1) 786 | 787 | const udx = new UDX() 788 | 789 | const socket = createSocket(t, udx) 790 | socket.bind(0, '127.0.0.1') 791 | 792 | const stream = udx.createStream(1) 793 | stream.write(b4a.alloc(0)) 794 | 795 | uncaught(function (error) { 796 | t.is(error.code, 'ERR_ASSERTION') 797 | 798 | stream.destroy() 799 | socket.close() 800 | }) 801 | }) 802 | 803 | test('backpressures stream', async function (t) { 804 | t.plan(2) 805 | 806 | const u = new UDX() 807 | 808 | const send = 64 * 1024 * 1024 809 | 810 | let sent = 0 811 | let recv = 0 812 | 813 | const socket = u.createSocket() 814 | socket.bind(0, '127.0.0.1') 815 | 816 | const a = u.createStream(1) 817 | const b = u.createStream(2) 818 | 819 | a.connect(socket, 2, socket.address().port) 820 | b.connect(socket, 1, socket.address().port) 821 | 822 | const rs = new Readable({ 823 | read (cb) { 824 | sent += 65536 825 | this.push(Buffer.alloc(65536)) 826 | if (sent === send) this.push(null) 827 | cb(null) 828 | } 829 | }) 830 | 831 | rs.pipe(a) 832 | 833 | b.resume() 834 | b.on('data', function (data) { 835 | recv += data.byteLength 836 | }) 837 | b.on('end', function () { 838 | t.is(recv, send) 839 | t.ok(send > 0, 'sanity check, sent ' + send + ' bytes') 840 | 841 | b.end() 842 | socket.close() 843 | }) 844 | 845 | b.on('error', (err) => t.fail('b errored: ' + err.message)) 846 | a.on('error', (err) => t.fail('a errored: ' + err.message)) 847 | }) 848 | 849 | test('UDX - basic stats', async function (t) { 850 | const tWave1 = t.test() 851 | tWave1.plan(1) 852 | 853 | const tWave2 = t.test() 854 | tWave2.plan(1) 855 | 856 | const [a, b] = makeTwoStreams(t) 857 | const aUdx = a.udx 858 | 859 | t.is(a.bytesTransmitted, 0, 'sanity check: init 0') 860 | t.is(a.packetsTransmitted, 0, 'sanity check: init 0') 861 | t.is(a.bytesReceived, 0, 'sanity check: init 0') 862 | t.is(a.packetsReceived, 0, 'sanity check: init 0') 863 | t.is(b.bytesTransmitted, 0, 'sanity check: init 0') 864 | t.is(b.packetsTransmitted, 0, 'sanity check: init 0') 865 | t.is(b.bytesReceived, 0, 'sanity check: init 0') 866 | t.is(b.packetsReceived, 0, 'sanity check: init 0') 867 | 868 | t.is(aUdx.bytesTransmitted, 0, 'sanity check: init 0') 869 | t.is(aUdx.packetsTransmitted, 0, 'sanity check: init 0') 870 | t.is(aUdx.bytesReceived, 0, 'sanity check: init 0') 871 | t.is(aUdx.packetsReceived, 0, 'sanity check: init 0') 872 | 873 | t.is(a.retransmits, 0, 'initialized to zero') 874 | t.is(a.rtoCount, 0, 'initialized to zero') 875 | t.is(a.fastRecoveries, 0, 'initialized to zero') 876 | 877 | let aNrDataEvents = 0 878 | a.on('data', function (data) { 879 | if (++aNrDataEvents === 1) { 880 | tWave1.pass('a received the first echo packet') 881 | } 882 | 883 | if (aNrDataEvents < 10) { 884 | b.write(b4a.from('creating imbalance'.repeat(100))) 885 | } 886 | 887 | if (aNrDataEvents === 10) { 888 | tWave2.pass('imbalance created') 889 | } 890 | }) 891 | 892 | b.on('data', function (data) { 893 | b.write(b4a.concat([b4a.from('echo: '), data])) 894 | }) 895 | 896 | a.write(b4a.from('hello world')) 897 | 898 | await tWave1 899 | 900 | // Pretty hard to calculate the exact amounts of expected packets/bytes 901 | // so we just sanity check the ballpark 902 | t.is(a.bytesTransmitted > 20, true, `a reasonable bytesTransmitted (${a.bytesTransmitted})`) 903 | t.is(a.packetsTransmitted > 0, true, `a reasonable packetsTransmitted (${a.packetsTransmitted})`) 904 | t.is(a.bytesReceived > 2, true, `a reasonable bytesReceived (${a.bytesReceived})`) 905 | t.is(a.packetsReceived > 0, true, `a reasonable packetsReceived (${a.packetsReceived})`) 906 | t.is(b.bytesTransmitted > 20, true, `b reasonable bytesTransmitted (${b.bytesTransmitted})`) 907 | t.is(b.packetsTransmitted > 0, true, `b reasonable packetsTransmitted (${b.packetsTransmitted})`) 908 | t.is(b.bytesReceived > 20, true, `b reasonable bytesReceived (${b.bytesReceived})`) 909 | t.is(b.packetsReceived > 0, true, `b reasonable packetsReceived (${b.packetsReceived})`) 910 | 911 | await tWave2 912 | a.end() 913 | b.end() 914 | 915 | t.is(a.bytesReceived > 1000, true, `a now higher bytesReceived (${a.bytesReceived})`) 916 | t.is(b.bytesTransmitted > 1000, true, `b now higher bytesTransmitted (${b.bytesTransmitted})`) 917 | 918 | t.is(aUdx.bytesTransmitted, a.bytesTransmitted, `udx same bytes out as the single stream (${aUdx.bytesTransmitted})`) 919 | t.is(aUdx.packetsTransmitted, a.packetsTransmitted, `udx same packets out as the single stream (${aUdx.packetsTransmitted})`) 920 | t.is(aUdx.bytesReceived, a.bytesReceived, `udx same bytes in as the single stream (${aUdx.bytesReceived})`) 921 | t.is(aUdx.packetsReceived, a.packetsReceived, true, `udx same packets in as the single stream (${aUdx.packetsReceived})`) 922 | 923 | const aSocket = a.socket 924 | t.is(aSocket.bytesTransmitted, a.bytesTransmitted, `udx socket same bytes out as the single stream (${aSocket.bytesTransmitted})`) 925 | t.is(aSocket.packetsTransmitted, a.packetsTransmitted, `udx socket same packets out as the single stream (${aSocket.packetsTransmitted})`) 926 | t.is(aSocket.bytesReceived, a.bytesReceived, `udx socket same bytes in as the single stream (${aSocket.bytesReceived})`) 927 | t.is(aSocket.packetsReceived, a.packetsReceived, true, `udx socket same packets in as the single stream (${aSocket.packetsReceived})`) 928 | }) 929 | -------------------------------------------------------------------------------- /test/udx.js: -------------------------------------------------------------------------------- 1 | const test = require('brittle') 2 | const UDX = require('../') 3 | 4 | test('lookup', async function (t) { 5 | const udx = new UDX() 6 | 7 | const address = await udx.lookup('localhost', { family: 4 }) 8 | 9 | t.is(address.host, '127.0.0.1') 10 | t.is(address.family, 4) 11 | }) 12 | 13 | test('lookup ipv6', async function (t) { 14 | const udx = new UDX() 15 | 16 | const address = await udx.lookup('localhost', { family: 6 }) 17 | 18 | t.is(address.host, '::1') 19 | t.is(address.family, 6) 20 | }) 21 | 22 | test('lookup invalid', async function (t) { 23 | const udx = new UDX() 24 | 25 | await t.exception(udx.lookup('example.invalid.')) 26 | }) 27 | 28 | test('network interfaces', async function (t) { 29 | const udx = new UDX() 30 | 31 | const interfaces = udx.networkInterfaces() 32 | 33 | t.ok(interfaces.length >= 1, 'has at least 1') 34 | 35 | for (let i = 0; i < interfaces.length; i++) { 36 | const n = interfaces[i] 37 | 38 | t.test(`interface ${i}`, async function (t) { 39 | t.ok(typeof n.name === 'string', `name: ${n.name}`) 40 | t.ok(typeof n.host === 'string', `host: ${n.host}`) 41 | t.ok(n.family === 4 || n.family === 6, `family: ${n.family}`) 42 | t.ok(typeof n.internal === 'boolean', `internal: ${n.internal}`) 43 | }) 44 | } 45 | }) 46 | 47 | test('network interfaces - watch, unwatch and destroy twice', async function (t) { 48 | t.plan(2) 49 | 50 | const udx = new UDX() 51 | 52 | const watcher = udx.watchNetworkInterfaces() 53 | watcher.watch() 54 | 55 | const totalInterfaces = udx.networkInterfaces().length 56 | t.is(totalInterfaces, watcher.interfaces.length) 57 | 58 | watcher.unwatch() 59 | watcher.unwatch() 60 | 61 | watcher.destroy() 62 | watcher.destroy() 63 | 64 | watcher.once('close', function () { 65 | t.pass() 66 | }) 67 | }) 68 | 69 | test('UDX - isIPv4', function (t) { 70 | t.is(UDX.isIPv4('127.0.0.1'), true) 71 | t.is(UDX.isIPv4('::1'), false) 72 | t.is(UDX.isIPv4('0.-1.0.0'), false) 73 | }) 74 | 75 | test('UDX - isIPv6', function (t) { 76 | t.is(UDX.isIPv6('127.0.0.1'), false) 77 | t.is(UDX.isIPv6('::1'), true) 78 | t.is(UDX.isIPv6('0.-1.0.0'), false) 79 | }) 80 | 81 | test('UDX - isIP', function (t) { 82 | t.is(UDX.isIP('127.0.0.1'), 4) 83 | t.is(UDX.isIP('::1'), 6) 84 | t.is(UDX.isIP('0.-1.0.0'), 0) 85 | }) 86 | --------------------------------------------------------------------------------