Compare commits

..

89 Commits
dap3 ... repl

Author SHA1 Message Date
jhmaster2000
7eba3229fe initial bun repl revision 2023-09-07 00:32:39 -03:00
jhmaster2000
5fe13cdaac fix bun discord command 2023-09-06 23:55:25 -03:00
Derrick Farris
7c82dc86ba Update development.md (#4480)
Remove comment about removed `BUN_OVERRIDE_MODULE_PATH` env var
2023-09-05 20:28:39 -07:00
Ciro Spaciari
125880af7d update root certs (#4499) 2023-09-05 20:28:20 -07:00
Dylan Conway
70a5cfe908 fix text decode trim (#4495)
* remove trim

* separate function

* a test

* trim when `stream` is true

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2023-09-05 17:53:31 -07:00
Jarred Sumner
1bd5b245b8 Align process.nextTick execution order with Node (#4409)
* Align `process.nextTick` execution order with Node

* some tests

* formatting

* fixups

* fix the test failures

* simplify the logic here

* push it up

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: dave caruso <me@paperdave.net>
2023-09-05 17:52:57 -07:00
dave caruso
acfd028e8f feat(runtime): Implement fs.watchFile (#4467)
* really lame prototype

* uses threads but badly

* it works i guess

* unwatchFile but lame

* it works

* test

* a

* aomitcs

* fix unwatching race condition

* use hasPendingActivity and GC stuff better

* test

* revert this
2023-09-05 17:41:39 -07:00
dave caruso
6f8a393492 fix(node:net): emit close event on connection error (#4336)
* emit close event on connection error

* re-review

* add test
2023-09-05 16:59:40 -07:00
Ciro Spaciari
6e50dd210f fix(fetch) always use readable stream if it is available (#4503)
* always use readable stream if it is available

* use bun sleep

* fix tests

* rm uws dep
2023-09-05 15:22:09 -07:00
Ciro Spaciari
d268097ded fix SSL proxy tunneling on fetch (#4510) 2023-09-05 15:21:34 -07:00
dave caruso
1e998c1bf2 fix(install): ensure all lockfile structs do not have undefined padding (#4401)
* padding sucks

* this assertion is already done elsewhere

* remove test. will be covered alex's pr i believe?

* fix webkit submodule

* fix uws submodule
2023-09-05 14:25:19 -07:00
Alex Lam S.L
bc2b55fdee fix checkout/build failure due to src/deps/uws (#4505) 2023-09-05 19:16:11 +03:00
Dylan Conway
7dae4db52a fix ipv6 localhost fetch (#4498)
* `node` null for localhost getaddrinfo

* more test
2023-09-05 06:12:54 -07:00
Alex Lam S.L
bcab2f9a0e minor rebuild diffs (#4486) 2023-09-05 14:49:07 +03:00
Jason
a85bd5d083 fix dup syscall on Windows (#4496) 2023-09-05 01:33:30 -07:00
Dylan Conway
f73f77d0de no need to chmod (#4490) 2023-09-04 17:16:37 -08:00
Ciro Spaciari
f1afd58338 fix zlib deflate on fetch (#4483)
* fix zlib deflate on fetch

* mention issue on test

* more tests

* oops
2023-09-04 17:37:47 -07:00
Ai Hoshino
5f34c44ec6 chore: fix typo (#4476)
Close: #4377
2023-09-04 12:35:08 -07:00
Ciro Spaciari
2d80f94eda fix(HTMLRewriter) buffer response before transform (#4418)
* html rewriter response buffering

* pipe the data when marked as used

* fix empty response

* add some fetch tests

* deinit parent stream

* fix decompression

* keep byte_reader alive

* update builds

* remove nonsense

* was not nonsense after all

* protect tmp ret value from GC, fix readable strong ref deinit/init

* fmt

* if we detach the stream we cannot update the fetch stream

* detach checking source

* more tests, progress with javascript and Direct sink

* drop support for pure readable stream for now

* more fixes

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2023-09-04 12:26:49 -07:00
Dylan Conway
18767906db initialize JSC for macros from cli 2023-09-03 20:49:06 -07:00
Ai Hoshino
f1b109d5dd fix(syscall): fix handling syscall errno (#4461)
* fix(syscall): fix handling syscall errno
Close: #4198

* remove unused code

* add more tests

* remove unused code
2023-09-03 17:30:30 -07:00
dave caruso
5c43744bce workaround a zig bug (#4440) 2023-09-02 01:39:39 -07:00
Karl Böhlmark
db2faf7c5b docs: fix http simple example log statement (#4320)
Co-authored-by: Karl Böhlmark <karl.bohlmark@netinsight.net>
2023-09-01 23:49:50 -07:00
Jorge Jiménez
bd690bb2b5 Fix typo (#4445)
Replace
`key: [Bun.file('./key1.pem'), Bun.file('./key2.pem']`
with
`key: [Bun.file('./key1.pem'), Bun.file('./key2.pem')]`
2023-09-01 23:49:09 -07:00
Dylan Conway
900f38d144 keep export star as (#4451) 2023-09-01 22:32:27 -07:00
Colin McDonnell
4920f458bc bun-vscode 0.0.8 2023-09-01 22:16:54 -07:00
Colin McDonnell
48cf9dddcb Update commands 2023-09-01 21:35:34 -07:00
Dylan Conway
0019073c8a fix Bun.serve with tls and Bun.file (#4450)
* check sendfile ctx

* add test

* undo blob check

* undo undo and add assert
2023-09-01 20:10:54 -07:00
Dylan Conway
45edb7bcf7 exclusive max 2023-09-01 20:09:45 -07:00
Ashcon Partovi
f0f91a2a31 Fix debug console from appears on start 2023-09-01 19:34:57 -07:00
Ashcon Partovi
94a4cda713 Add configuration options to extension 2023-09-01 18:56:25 -07:00
Ashcon Partovi
eeb683d977 Fix run button starting cwd at / 2023-09-01 15:35:14 -07:00
dave caruso
0fd0ad993b fix(runtime): fix dns_resolver crash (#4435)
* fix incorrect c pointer

* format

* lets go

* random other test case fixed

* hanassagi patch

* Update dns_resolver.zig

* Revert "Update dns_resolver.zig"

This reverts commit 53eb338048.

* See if the tests pass

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2023-09-01 15:27:59 -07:00
Ashcon Partovi
27de8bbf7b Fix background color 2023-09-01 13:55:19 -07:00
Ashcon Partovi
6bdf24c8ef Allow older versions of VSCode 2023-09-01 13:49:52 -07:00
Ashcon Partovi
57caea5ae7 Fix README for extension 2023-09-01 13:40:48 -07:00
Ashcon Partovi
fe3be666bc Update VSCode extension 2023-09-01 13:34:29 -07:00
Ashcon Partovi
2b421be06f Fix breakpoint on entry for extension 2023-09-01 10:19:14 -07:00
Ashcon Partovi
1ff8155c28 Add Bun.canReload event to inspector 2023-09-01 00:33:33 -07:00
Ashcon Partovi
40d749b480 JavaScript Debug Terminal == Bun Terminal 2023-08-31 23:19:04 -07:00
dave caruso
5288178c86 fix(runtime): fs.cp edge cases (#4439)
* yippee

* enable cpSync tests

* much better

* that doesnt actually do anything

* lose
2023-08-31 23:04:19 -07:00
Dylan Conway
6ae4dd808b only set initial debugger breakpoint once (#4441)
* unset `set_breakpoint_on_first_line` on reload

* move to `module_loader.zig`
2023-08-31 23:03:44 -07:00
Ashcon Partovi
b07bb3ce17 Make breakpoints faster in VSCode extension 2023-08-31 21:42:11 -07:00
Julian
59edbe645c bun install correctly join dependency URLs (#4421)
* use WTF to join registry strings

* show dependency error messages, better join error

We actually report errors when enqueuing dependencies now. I also made
the join URLs error message read better. It'd be cleaner to handle it
all in one place, but there's currently no way to propagate the data up.

* starting on registry URL tests

* added more registry URL tests

* [install] prevent optional/peer deps from failing builds

Couldn't get the peer dependency test to work, but the code is there.

* ran prettier

* changed error note to use realname, updated tests

* ran prettier again...
2023-08-31 17:36:03 -07:00
Dylan Conway
fef70f2473 get name if not provided in FormData.append (#4434)
* get file name from blob if not provided

* add test

* another test

* format
2023-08-31 17:33:08 -07:00
Ashcon Partovi
bd7262f037 Fix vscode debug terminal 2023-08-31 00:18:59 -07:00
Jarred Sumner
10c8013ef2 Create cp-r.mjs 2023-08-30 22:18:04 -07:00
Jarred Sumner
82d26dd9bd Add bench 2023-08-30 21:00:44 -07:00
Ai Hoshino
9334fbe9b4 fix(install): resolve semver matching with pre-release tags. (#4412)
Close: #4398
2023-08-30 18:49:09 -07:00
dave caruso
0a5d2a8195 feat(node:fs): add cp/cpSync/promises.cp + async copyFile (#4340)
* half working disaster code

* this

* async copyFile

* .

* its failing symlink tests

* asdfg

* asdf

* hmm

* okay i think ti works

* small edits

* fix test on linux

* i hate atomics / atomics hate me back <3

* add a message in the builtins bundler that 0.8 is needed. it breaks on older versions lol.

* fixed

* rebase
2023-08-30 18:30:06 -07:00
Dylan Conway
89f24e66ff add life cycle scripts to lockfile metahash (#4420)
* add life cycle scripts to lockfile metahash

* Update lockfile.zig
2023-08-30 18:29:08 -07:00
Alex Lam S.L
037463fc48 [install] fix stale root life-cycle script in lockfile (#4411)
fixes #4319
2023-08-30 17:35:28 -07:00
Dylan Conway
04215e2f3a reset tty at exit (#4419) 2023-08-30 16:38:25 -07:00
Alex Lam S.L
c19714aff7 minor rebuild diffs (#4416) 2023-08-30 14:48:30 -07:00
Ciro Spaciari
908018a4df fix(http/https) disable decompress on http/https client (#4399)
* disable decompress on http/https module

* make js again
2023-08-30 13:50:09 -03:00
Jarred Sumner
f24ca39004 Fix bug in util/types.{isGeneratorFunction,isAsyncFunction} 2023-08-30 00:19:39 -07:00
Jarred Sumner
e3dc5b6b4c reset signal handlers in Bun.spawn (#4405)
* see if this fixes it

* We don't need this

* Remove extra flag

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-30 00:16:08 -07:00
Ashcon Partovi
f2553d2454 More support for DAP (#4380)
* Fix reconnect with --watch

* Support setVariable

* Support setExpression

* Support watch variables

* Conditional and hit breakpoints

* Support exceptionInfo

* Support goto and gotoTargets

* Support completions

* Support both a URL and UNIX inspector at the same time

* Fix url

* WIP, add timeouts to figure out issue

* Fix messages being dropped from debugger.ts

* Progress

* Fix breakpoints and ref-event-loop

* More fixes

* Fix exit

* Make hovers better

* Fix --hot
2023-08-29 23:44:39 -07:00
Jarred Sumner
c028b206bc Fix assertion failure in spawn-related tests (#4400)
* Clean up some of the event loop code

* Support timeouts

* Defer freeing FilePoll

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-29 21:17:56 -07:00
dave caruso
a846852818 fix(node-fetch): use stream.Readable instead of web streams (#4394)
* fix blobFrom

* fix(node-fetch): use stream.Readable instead of web streams

* uncomment

* comment why
2023-08-29 19:45:16 -07:00
Ai Hoshino
3f4bc625ff parse unix socket path param in http.server (#4390) 2023-08-29 19:39:12 -07:00
Jarred Sumner
5d84ef7780 Rename uws_event_loop to event_loop_handle 2023-08-29 15:31:06 -07:00
Jarred Sumner
a77ed151af [git] Normalize line endings 2023-08-29 15:30:07 -07:00
Jarred Sumner
cf151a256c Enforce unix line endings in git 2023-08-29 15:29:09 -07:00
Dylan Conway
de58e9d583 emit open and call close callback (#4384) 2023-08-29 09:44:45 -07:00
Dylan Conway
07d8623976 fix #4356 (#4386) 2023-08-29 01:49:12 -07:00
Ai Hoshino
c53372c9f3 feat(node:dns): implement dns.reverse. (#4332)
* feat(node:dns): implement `dns.reverse`.
Close: #4299

* fix dns reverse for ipv6

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2023-08-28 20:10:33 -07:00
Dylan Conway
d1c2d6b25c use options.fd if provided for fs.Read/WriteStream (#4378)
* use `options.fd` over path

* tests

* fix `@clack/prompts`

* == null
2023-08-28 20:08:08 -07:00
Colin McDonnell
a2aad40171 docs: use table tag 2023-08-28 13:05:23 -07:00
Colin McDonnell
177e02b304 docs: hot reloading with Bun.serve 2023-08-28 12:55:22 -07:00
Colin McDonnell
726f8aa3ef Update nuxi output 2023-08-28 12:24:51 -07:00
Prabhat Sachdeva
d3626287bd remove uws from .gitmodules (#4374) 2023-08-28 10:39:45 -07:00
Jarred Sumner
a2ddfe6913 Bring uSockets & uWebSockets forks into Bun's repository (#4372)
* Move uWebSockets and uSockets forks into Bun's repository

* Update Makefile

* Update settings.json

* Update libuwsockets.cpp

* Remove backends we won't be using

* Update bindings.cpp

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-28 08:38:30 -07:00
Ciro Spaciari
6e4a1f2918 make pending_response and metdata life cycle more clear and make fetch more reliable (#4331)
* make pending_response and metdata life cycle more clear

* typo

* WIP: memory investigation

* check zlib and fix zlib

* use state allocator for metadata

* remove postBodyProcess

* undo some test things

* fix race condition

* fix removing compressed header

* some extra checks

* remove arenas on zlib and comment repoter.assert because of toOwnedSliceZ

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2023-08-28 07:21:46 -07:00
Jarred Sumner
ebfaa682f7 More renaming 2023-08-28 04:53:51 -07:00
Jarred Sumner
277e5c0b77 mv src/bun.js/node/syscall.zig -> src/sys.zig 2023-08-28 04:53:18 -07:00
Jarred Sumner
e2a17344dc just kernel32 things (#4354)
* just kernel32 things

* more

* Update linux_c.zig

* Update windows_c.zig

* Add workaround

Workaround https://github.com/ziglang/zig/issues/16980

* Rename http.zig to bun_dev_http_server.zig

* Rename usages

* more

* more

* more

* thanks tigerbeetle

* Rename `JSC.Node.Syscall` -> `bun.sys`

* more

* woops

* more!

* hmm

* it says there are only 37 errors, but that's not true

* populate argv

* it says 32 errors!

* 24 errors

* fix regular build

* 12 left!

* Still 12 left!

* more

* 2 errors left...

* 1 more error

* Add link to Tigerbeetle

* Fix the remainign error

* Fix test timeout

* Update syscall.zig

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-28 04:39:16 -07:00
Ai Hoshino
efe987e8d1 Fix some edge cases in the env param of spawn. (#4364)
Close: #4362
2023-08-27 23:58:31 -07:00
Jarred Sumner
ed5dc5bbf9 @electroid's dap changes (#4367)
* Rework terminal, launch is still WIP

* Use the proper Terminal profile API

* More changes

* progress

---------

Co-authored-by: Ashcon Partovi <ashcon@partovi.net>
2023-08-27 09:03:15 -07:00
Jarred Sumner
36f9d2291c Workaround zig wasm bug 2023-08-26 21:09:08 -07:00
Jarred Sumner
9e653e610c Update WebKit 2023-08-26 03:10:16 -07:00
Jarred Sumner
f16d729c76 Set the timezone in spawn (#4337)
Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-26 02:35:10 -07:00
Ashcon Partovi
2a9e967fd1 More improvements to debugger support (#4345)
* More fixes for dap

* More changes

* More changes 2

* More fixes

* Fix debugger.ts

* Bun Terminal
2023-08-26 02:34:25 -07:00
Ai Hoshino
910daeff27 Fix the crash when importing a module that does not exist. (#4348)
Close: #4240
2023-08-26 01:14:40 -07:00
Jarred Sumner
e1dacf88d0 don't get too excited but there are at least hundreds of compiler errors when you run this command 2023-08-25 22:05:02 -07:00
Jarred Sumner
d72763dc29 bump! 2023-08-25 21:54:16 -07:00
Jarred Sumner
d98a93c318 Automatically hot reload Bun.serve() (#4344)
* Automatically hot reload Bun.serve()

* Update doc

* Update example

---------

Co-authored-by: Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com>
2023-08-25 21:08:41 -07:00
Colin McDonnell
f70bb2497b Fix link 2023-08-25 12:50:13 -07:00
Colin McDonnell
d62b0c3652 Readability tweaks 2023-08-25 12:47:47 -07:00
29519 changed files with 252430 additions and 11407 deletions

8
.editorconfig Normal file
View File

@@ -0,0 +1,8 @@
# https://EditorConfig.org
root = true
[*]
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf

22
.gitattributes vendored
View File

@@ -1,3 +1,22 @@
*.css text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.js text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.jsx text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.tsx text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.ts text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.c text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.cpp text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.cc text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.yml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.zig text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.rs text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.h text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.json text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.lock text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.map text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.md text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mjs text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.mts text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
.vscode/launch.json linguist-generated
src/api/schema.d.ts linguist-generated
fixture.*.c linguist-generated
@@ -5,7 +24,6 @@ src/api/schema.js linguist-generated
src/bun.js/bindings/sqlite/sqlite3.c linguist-vendored
src/bun.js/bindings/sqlite/sqlite3_local.h linguist-vendored
*.lockb binary diff=lockb
*.zig text eol=lf
src/bun.js/bindings/simdutf.cpp linguist-vendored
src/bun.js/bindings/simdutf.h linguist-vendored
@@ -31,3 +49,5 @@ src/bun.js/bindings/ZigGeneratedClasses+lazyStructureHeader.h linguist-generated
src/bun.js/bindings/ZigGeneratedClasses+lazyStructureImpl.h linguist-generated
docs/**/* linguist-documentation
packages/bun-uws/fuzzing/seed-corpus/**/* linguist-generated

7
.gitmodules vendored
View File

@@ -48,13 +48,6 @@ ignore = dirty
depth = 1
shallow = true
fetchRecurseSubmodules = false
[submodule "src/deps/uws"]
path = src/deps/uws
url = https://github.com/Jarred-Sumner/uWebSockets
ignore = dirty
depth = 1
shallow = true
fetchRecurseSubmodules = true
[submodule "src/deps/tinycc"]
path = src/deps/tinycc
url = https://github.com/Jarred-Sumner/tinycc.git

3
.scripts/write-versions.sh Normal file → Executable file
View File

@@ -7,11 +7,9 @@ LIBARCHIVE_VERSION=$(git rev-parse HEAD:./src/deps/libarchive)
PICOHTTPPARSER_VERSION=$(git rev-parse HEAD:./src/deps/picohttpparser)
BORINGSSL_VERSION=$(git rev-parse HEAD:./src/deps/boringssl)
ZLIB_VERSION=$(git rev-parse HEAD:./src/deps/zlib)
UWS_VERSION=$(git rev-parse HEAD:./src/deps/uws)
LOLHTML=$(git rev-parse HEAD:./src/deps/lol-html)
TINYCC=$(git rev-parse HEAD:./src/deps/tinycc)
C_ARES=$(git rev-parse HEAD:./src/deps/c-ares)
USOCKETS=$(cd src/deps/uws/uSockets && git rev-parse HEAD)
rm -rf src/generated_versions_list.zig
echo "// AUTO-GENERATED FILE. Created via .scripts/write-versions.sh" >src/generated_versions_list.zig
@@ -20,7 +18,6 @@ echo "pub const boringssl = \"$BORINGSSL_VERSION\";" >>src/generated_versions_li
echo "pub const libarchive = \"$LIBARCHIVE_VERSION\";" >>src/generated_versions_list.zig
echo "pub const mimalloc = \"$MIMALLOC_VERSION\";" >>src/generated_versions_list.zig
echo "pub const picohttpparser = \"$PICOHTTPPARSER_VERSION\";" >>src/generated_versions_list.zig
echo "pub const uws = \"$UWS_VERSION\";" >>src/generated_versions_list.zig
echo "pub const webkit = \"$WEBKIT_VERSION\";" >>src/generated_versions_list.zig
echo "pub const zig = @import(\"std\").fmt.comptimePrint(\"{}\", .{@import(\"builtin\").zig_version});" >>src/generated_versions_list.zig
echo "pub const zlib = \"$ZLIB_VERSION\";" >>src/generated_versions_list.zig

View File

@@ -21,7 +21,8 @@
"${workspaceFolder}/src/deps/boringssl/include/",
"${workspaceFolder}/src/deps",
"${workspaceFolder}/src/napi/*",
"${workspaceFolder}/src/deps/uws/uSockets/src"
"${workspaceFolder}/packages/bun-usockets/src",
"${workspaceFolder}/packages/"
],
"browse": {
"path": [
@@ -43,7 +44,8 @@
"${workspaceFolder}/src/bun.js/modules/*",
"${workspaceFolder}/src/deps",
"${workspaceFolder}/src/deps/boringssl/include/",
"${workspaceFolder}/src/deps/uws/uSockets/src",
"${workspaceFolder}/packages/bun-usockets/",
"${workspaceFolder}/packages/bun-uws/",
"${workspaceFolder}/src/napi"
],
"limitSymbolsToIncludedHeaders": true,

19
.vscode/launch.json generated vendored
View File

@@ -4,8 +4,6 @@
// it makes our tests very slow
// But it helps catch memory bugs
// SIGHUP must be ignored or the debugger will pause when a spawned subprocess exits:
// { "initCommands": ["process handle -p false -s false -n false SIGHUP"] }
"version": "0.2.0",
"configurations": [
{
@@ -21,7 +19,6 @@
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -36,7 +33,6 @@
"FORCE_COLOR": "1",
"BUN_DEBUG_QUIET_LOGS": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
@@ -51,7 +47,6 @@
"env": {
"FORCE_COLOR": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -66,7 +61,6 @@
"FORCE_COLOR": "1",
"BUN_DEBUG_QUIET_LOGS": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -81,7 +75,6 @@
"FORCE_COLOR": "1",
"BUN_DEBUG_QUIET_LOGS": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -96,7 +89,6 @@
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -111,7 +103,6 @@
"FORCE_COLOR": "1",
"BUN_DEBUG_QUIET_LOGS": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -126,7 +117,6 @@
"FORCE_COLOR": "1",
"BUN_DEBUG_QUIET_LOGS": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -140,7 +130,6 @@
"FORCE_COLOR": "1",
"NODE_ENV": "development"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -155,7 +144,6 @@
"BUN_DEBUG_QUIET_LOGS": "1",
"BUN_GARBAGE_COLLECTOR_LEVEL": "2"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -168,7 +156,6 @@
"env": {
"FORCE_COLOR": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -181,7 +168,6 @@
"env": {
"FORCE_COLOR": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -194,7 +180,6 @@
"env": {
"FORCE_COLOR": "1"
},
"initCommands": ["process handle -p false -s false -n false SIGHUP"],
"console": "internalConsole"
},
{
@@ -216,9 +201,7 @@
"console": "internalConsole",
"env": {
"BUN_CONFIG_MINIFY_WHITESPACE": "1"
},
// SIGHUP must be ignored or the debugger will pause when a spawned subprocess exits.
"initCommands": ["process handle -p false -s false -n false SIGHUP"]
}
},
{
"type": "lldb",

View File

@@ -79,7 +79,8 @@
"src/deps/tinycc": true,
"src/deps/zstd": true,
"test/snippets/package-json-exports/_node_modules_copy": true,
"src/js/out": true
"src/js/out": true,
"src/packages/bun-uws/fuzzing/seed-corpus/": true
},
"C_Cpp.files.exclude": {
"**/.vscode": true,

View File

@@ -284,7 +284,8 @@ ARG CPU_TARGET
ENV CPU_TARGET=${CPU_TARGET}
COPY Makefile ${BUN_DIR}/Makefile
COPY src/deps/uws ${BUN_DIR}/src/deps/uws
COPY packages/bun-uws ${BUN_DIR}/packages/bun-uws
COPY packages/bun-usockets ${BUN_DIR}/packages/bun-usockets
COPY src/deps/zlib ${BUN_DIR}/src/deps/zlib
COPY src/deps/boringssl/include ${BUN_DIR}/src/deps/boringssl/include
COPY src/deps/libuwsockets.cpp ${BUN_DIR}/src/deps/libuwsockets.cpp
@@ -293,7 +294,7 @@ COPY src/deps/_libusockets.h ${BUN_DIR}/src/deps/_libusockets.h
WORKDIR $BUN_DIR
RUN cd $BUN_DIR && \
make uws && rm -rf src/deps/uws Makefile
make uws && rm -rf packages/bun-uws Makefile
FROM bun-base as base64

View File

@@ -349,7 +349,7 @@ LINUX_INCLUDE_DIRS := $(ALL_JSC_INCLUDE_DIRS) \
-I$(ZLIB_INCLUDE_DIR)
UWS_INCLUDE_DIR := -I$(BUN_DEPS_DIR)/uws/uSockets/src -I$(BUN_DEPS_DIR)/uws/src -I$(BUN_DEPS_DIR)
UWS_INCLUDE_DIR := -I$(BUN_DIR)/packages/bun-usockets/src -I$(BUN_DIR)/packages -I$(BUN_DEPS_DIR)
INCLUDE_DIRS := $(UWS_INCLUDE_DIR) -I$(BUN_DEPS_DIR)/mimalloc/include -I$(BUN_DEPS_DIR)/zstd/include -Isrc/napi -I$(BUN_DEPS_DIR)/boringssl/include -I$(BUN_DEPS_DIR)/c-ares/include -Isrc/bun.js/modules
@@ -760,17 +760,17 @@ build-obj-safe:
UWS_CC_FLAGS = -pthread -DLIBUS_USE_OPENSSL=1 -DUWS_HTTPRESPONSE_NO_WRITEMARK=1 -DLIBUS_USE_BORINGSSL=1 -DWITH_BORINGSSL=1 -Wpedantic -Wall -Wextra -Wsign-conversion -Wconversion $(UWS_INCLUDE) -DUWS_WITH_PROXY
UWS_CXX_FLAGS = $(UWS_CC_FLAGS) -std=$(CXX_VERSION) -fno-exceptions -fno-rtti
UWS_LDFLAGS = -I$(BUN_DEPS_DIR)/boringssl/include -I$(ZLIB_INCLUDE_DIR)
USOCKETS_DIR = $(BUN_DEPS_DIR)/uws/uSockets/
USOCKETS_SRC_DIR = $(BUN_DEPS_DIR)/uws/uSockets/src/
USOCKETS_DIR = $(BUN_DIR)/packages/bun-usockets
USOCKETS_SRC_DIR = $(USOCKETS_DIR)/src
usockets:
rm -rf $(BUN_DEPS_DIR)/uws/uSockets/*.o $(BUN_DEPS_DIR)/uws/uSockets/**/*.o $(BUN_DEPS_DIR)/uws/uSockets/*.a $(BUN_DEPS_DIR)/uws/uSockets/*.bc
cd $(USOCKETS_DIR) && $(CC_WITH_CCACHE) -fno-builtin-malloc -fno-builtin-free -fno-builtin-realloc $(EMIT_LLVM_FOR_RELEASE) $(MACOS_MIN_FLAG) -fPIC $(CFLAGS) $(UWS_CC_FLAGS) -save-temps -I$(BUN_DEPS_DIR)/uws/uSockets/src $(UWS_LDFLAGS) -g $(DEFAULT_LINKER_FLAGS) $(PLATFORM_LINKER_FLAGS) $(OPTIMIZATION_LEVEL) -c $(wildcard $(USOCKETS_SRC_DIR)/*.c) $(wildcard $(USOCKETS_SRC_DIR)/**/*.c)
cd $(USOCKETS_DIR) && $(CXX_WITH_CCACHE) -fno-builtin-malloc -fno-builtin-free -fno-builtin-realloc $(EMIT_LLVM_FOR_RELEASE) $(MACOS_MIN_FLAG) -fPIC $(CXXFLAGS) $(UWS_CXX_FLAGS) -save-temps -I$(BUN_DEPS_DIR)/uws/uSockets/src $(UWS_LDFLAGS) -g $(DEFAULT_LINKER_FLAGS) $(PLATFORM_LINKER_FLAGS) $(OPTIMIZATION_LEVEL) -c $(wildcard $(USOCKETS_SRC_DIR)/*.cpp) $(wildcard $(USOCKETS_SRC_DIR)/**/*.cpp)
rm -rf $(USOCKETS_DIR)/*.i $(USOCKETS_DIR)/*.bc $(USOCKETS_DIR)/*.o $(USOCKETS_DIR)/*.s $(USOCKETS_DIR)/*.ii $(USOCKETS_DIR)/*.s
cd $(USOCKETS_DIR) && $(CC_WITH_CCACHE) -I$(USOCKETS_SRC_DIR) -fno-builtin-malloc -fno-builtin-free -fno-builtin-realloc $(EMIT_LLVM_FOR_RELEASE) $(MACOS_MIN_FLAG) -fPIC $(CFLAGS) $(UWS_CC_FLAGS) -save-temps -I$(BUN_DEPS_DIR)/uws/uSockets/src $(UWS_LDFLAGS) -g $(DEFAULT_LINKER_FLAGS) $(PLATFORM_LINKER_FLAGS) $(OPTIMIZATION_LEVEL) -c $(wildcard $(USOCKETS_SRC_DIR)/*.c) $(wildcard $(USOCKETS_SRC_DIR)/**/*.c)
cd $(USOCKETS_DIR) && $(CXX_WITH_CCACHE) -I$(USOCKETS_SRC_DIR) -fno-builtin-malloc -fno-builtin-free -fno-builtin-realloc $(EMIT_LLVM_FOR_RELEASE) $(MACOS_MIN_FLAG) -fPIC $(CXXFLAGS) $(UWS_CXX_FLAGS) -save-temps -I$(BUN_DEPS_DIR)/uws/uSockets/src $(UWS_LDFLAGS) -g $(DEFAULT_LINKER_FLAGS) $(PLATFORM_LINKER_FLAGS) $(OPTIMIZATION_LEVEL) -c $(wildcard $(USOCKETS_SRC_DIR)/*.cpp) $(wildcard $(USOCKETS_SRC_DIR)/**/*.cpp)
cd $(USOCKETS_DIR) && $(AR) rcvs $(BUN_DEPS_OUT_DIR)/libusockets.a $(USOCKETS_DIR)/*.{o,bc}
uws: usockets
$(CXX_WITH_CCACHE) -O2 $(EMIT_LLVM_FOR_RELEASE) -fPIC -I$(BUN_DEPS_DIR)/uws/uSockets/src $(CLANG_FLAGS) $(CFLAGS) $(UWS_CXX_FLAGS) $(UWS_LDFLAGS) $(PLATFORM_LINKER_FLAGS) -c -I$(BUN_DEPS_DIR) $(BUN_DEPS_OUT_DIR)/libusockets.a $(BUN_DEPS_DIR)/libuwsockets.cpp -o $(BUN_DEPS_OUT_DIR)/libuwsockets.o
$(CXX_WITH_CCACHE) -O2 $(EMIT_LLVM_FOR_RELEASE) -fPIC -I$(USOCKETS_SRC_DIR) $(CLANG_FLAGS) $(CFLAGS) $(UWS_CXX_FLAGS) $(UWS_LDFLAGS) $(PLATFORM_LINKER_FLAGS) -c -I$(BUN_DEPS_DIR) $(BUN_DEPS_OUT_DIR)/libusockets.a $(BUN_DEPS_DIR)/libuwsockets.cpp -o $(BUN_DEPS_OUT_DIR)/libuwsockets.o
.PHONY: sign-macos-x64
sign-macos-x64:
@@ -900,7 +900,8 @@ check-glibc-version-dependency:
ifeq ($(OS_NAME),darwin)
zig-win32:
$(ZIG) build -Dtarget=x86_64-windows
# Hardened runtime will not work with debugging
bun-codesign-debug:

3
bench/snippets/cp-r.mjs Normal file
View File

@@ -0,0 +1,3 @@
import { cp } from "fs/promises";
await cp(process.argv[2], process.argv[3], { recursive: true });

31
bench/snippets/cp.mjs Normal file
View File

@@ -0,0 +1,31 @@
import { mkdirSync, writeFileSync } from "fs";
import { bench, run } from "./runner.mjs";
import { cp } from "fs/promises";
import { join } from "path";
import { tmpdir } from "os";
const hugeDirectory = (() => {
const root = join(tmpdir(), "huge");
const base = join(root, "directory", "for", "benchmarks", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10");
mkdirSync(base, {
recursive: true,
});
for (let i = 0; i < 1000; i++) {
writeFileSync(join(base, "file-" + i + ".txt"), "Hello, world! " + i);
}
return root;
})();
const hugeFilePath = join(tmpdir(), "huge-file-0.txt");
const hugeText = "Hello, world!".repeat(1000000);
writeFileSync(hugeFilePath, hugeText);
var hugeCopyI = 0;
bench("cp -r (1000 files)", async b => {
await cp(hugeDirectory, join(tmpdir(), "huge-copy" + hugeCopyI++), { recursive: true });
});
bench("cp 1 " + ((hugeText.length / 1024) | 0) + " KB file", async b => {
await cp(hugeFilePath, join(tmpdir(), "huge-file" + hugeCopyI++));
});
await run();

View File

@@ -34,6 +34,10 @@ fn addInternalPackages(b: *Build, step: *CompileStep, _: std.mem.Allocator, _: [
break :brk b.createModule(.{
.source_file = FileSource.relative("src/io/io_linux.zig"),
});
} else if (target.isWindows()) {
break :brk b.createModule(.{
.source_file = FileSource.relative("src/io/io_windows.zig"),
});
}
break :brk b.createModule(.{
@@ -132,6 +136,16 @@ const Module = std.build.Module;
const fs = std.fs;
pub fn build(b: *Build) !void {
build_(b) catch |err| {
if (@errorReturnTrace()) |trace| {
std.debug.dumpStackTrace(trace.*);
}
return err;
};
}
pub fn build_(b: *Build) !void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
@@ -187,12 +201,12 @@ pub fn build(b: *Build) !void {
else
"root.zig";
const min_version: std.SemanticVersion = if (target.getOsTag() != .freestanding)
const min_version: std.SemanticVersion = if (!(target.isWindows() or target.getOsTag() == .freestanding))
target.getOsVersionMin().semver
else
.{ .major = 0, .minor = 0, .patch = 0 };
const max_version: std.SemanticVersion = if (target.getOsTag() != .freestanding)
const max_version: std.SemanticVersion = if (!(target.isWindows() or target.getOsTag() == .freestanding))
target.getOsVersionMax().semver
else
.{ .major = 0, .minor = 0, .patch = 0 };
@@ -206,6 +220,8 @@ pub fn build(b: *Build) !void {
.main_pkg_path = .{ .cwd_relative = b.pathFromRoot(".") },
});
b.reference_trace = 16;
var default_build_options: BunBuildOptions = brk: {
const is_baseline = arch.isX86() and (target.cpu_model == .baseline or
!std.Target.x86.featureSetHas(target.getCpuFeatures(), .avx2));

View File

@@ -189,7 +189,7 @@ Bun.serve({
});
```
## Hot reloading
## Object syntax
Thus far, the examples on this page have used the explicit `Bun.serve` API. Bun also supports an alternate syntax.
@@ -203,15 +203,15 @@ export default {
} satisfies Serve;
```
Instead of passing the server options into `Bun.serve`, export it. This file can be executed as-is; when Bun runs a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood.
Instead of passing the server options into `Bun.serve`, `export default` it. This file can be executed as-is; when Bun sees a file with a `default` export containing a `fetch` handler, it passes it into `Bun.serve` under the hood.
This syntax has one major advantage: it is hot-reloadable out of the box. When any source file is changed, Bun will reload the server with the updated code _without restarting the process_. This makes hot reloads nearly instantaneous. Use the `--hot` flag when starting the server to enable hot reloading.
<!-- This syntax has one major advantage: it is hot-reloadable out of the box. When any source file is changed, Bun will reload the server with the updated code _without restarting the process_. This makes hot reloads nearly instantaneous. Use the `--hot` flag when starting the server to enable hot reloading. -->
```bash
<!-- ```bash
$ bun --hot server.ts
```
``` -->
It's possible to configure hot reloading while using the explicit `Bun.serve` API; for details refer to [Runtime > Hot reloading](/docs/runtime/hot).
<!-- It's possible to configure hot reloading while using the explicit `Bun.serve` API; for details refer to [Runtime > Hot reloading](/docs/runtime/hot). -->
## Streaming files

View File

@@ -95,7 +95,7 @@ Bun.listen({
// string
key: fs.readFileSync("./key.pem", "utf8"),
// array of above
key: [Bun.file('./key1.pem'), Bun.file('./key2.pem']
key: [Bun.file('./key1.pem'), Bun.file('./key2.pem')]
},
});
```

View File

@@ -6,28 +6,18 @@ Bun supports [Nuxt](https://nuxt.com) out of the box. Initialize a Nuxt app with
```sh
$ bunx nuxi init my-nuxt-app
Nuxi 3.6.5
✨ Nuxt project is created with v3 template. Next steps:
cd my-nuxt-app
Install dependencies with npm install or yarn install or pnpm install
Start development server with npm run dev or yarn dev or pnpm run dev
```
---
Then move into the project directory and install dependencies.
```sh
$ cd my-app
$ bun install
bun install v0.8.0
+ @nuxt/devtools@0.8.0
+ @types/node@18.17.6
+ nuxt@3.6.5
Nuxi 3.6.5
✔ Which package manager would you like to use?
bun
◐ Installing dependencies...
bun install v0.8.1 (16b4bf34)
+ @nuxt/devtools@0.8.2
+ nuxt@3.7.0
785 packages installed [2.67s]
✔ Installation completed.
✔ Types generated in .nuxt
776 packages installed [1.72s]
✨ Nuxt project has been created with the v3 template. Next steps:
cd my-nuxt-app
Start development server with bun run dev
```
---
@@ -39,6 +29,7 @@ The `nuxt` CLI uses Node.js by default; passing the `--bun` flag forces the dev
{% /callout %}
```
$ cd my-nuxt-app
$ bun --bun run dev
$ nuxt dev
Nuxi 3.6.5

View File

@@ -10,46 +10,13 @@ bun --hot run index.ts
---
To avoid re-running `Bun.serve()` during `--hot` reloads, you should assign the `Server` instance as a property of `globalThis`. The `globalThis` object survives hot reloads.
Bun detects when you are running an HTTP server with `Bun.serve()`. It reloads your fetch handler when source files change, _without_ restarting the `bun` process. This makes hot reloads nearly instantaneous.
```ts
import { type Serve, type Server } from "bun";
// make TypeScript happy
declare global {
var server: Server;
}
// define server parameters
const serveOptions: Serve = {
Bun.serve({
port: 3000,
fetch(req) {
return new Response(`Hello world`);
},
};
if (!globalThis.server) {
globalThis.server = Bun.serve(serveOptions);
} else {
globalThis.server.reload(serveOptions);
}
```
---
To avoid manually calling `server.reload()`, you can use start a server with Bun's [object syntax](/docs/runtime/hot#http-servers). If you `export default` a plain object with a `fetch` handler defined, then run this file with Bun, Bun will start an HTTP server as if you'd passed this object into `Bun.serve()`.
With this approach, Bun automatically reloads the server when reloads happen.
See [HTTP > Hot Reloading](<[/docs/api/http](https://bun.sh/docs/api/http#hot-reloading)>) for full docs.
```ts
import { type Serve } from "bun";
export default {
port: 3000,
fetch(req) {
return new Response(`Hello world`);
},
} satisfies Serve;
});
```

View File

@@ -14,5 +14,5 @@ const server = Bun.serve({
},
});
console.log(`Listening on localhost:\${server.port}`);
console.log(`Listening on localhost: ${server.port}`);
```

View File

@@ -5,7 +5,6 @@ name: Bail early with the Bun test runner
Use the `--bail` flag to bail on a test run after a single failure. This is useful for aborting as soon as possible in a continuous integration environment.
```sh
# re-run each test 10 times
$ bun test --bail
```

View File

@@ -45,6 +45,8 @@ $ echo $?
1 # this is the exit code of the previous command
```
---
Different thresholds can be set for line-level and function-level coverage.
```toml

View File

@@ -2,7 +2,7 @@
name: Generate code coverage reports with the Bun test runner
---
Bun's test runner supports built-in _code coverage reporting_. This makes it easy to see how much of the codebase is covered by tests, and find areas that are not currently well-tested.
Bun's test runner supports built-in _code coverage reporting_. This makes it easy to see how much of the codebase is covered by tests and find areas that are not currently well-tested.
---

View File

@@ -14,7 +14,7 @@ $ bun add -d @happy-dom/global-registrator
---
This module exports a "registrator" that will adds the mocked browser APIs to the global scope.
This module exports a "registrator" that injects the mocked browser APIs to the global scope.
```ts#happydom.ts
import { GlobalRegistrator } from "@happy-dom/global-registrator";
@@ -24,7 +24,7 @@ GlobalRegistrator.register();
---
We need to make sure this file is executed before any of our test files. That's a job for Bun's built-in _preload_ functionality. Create a `bunfig.toml` file in the root of your project (if it doesn't already exist) and add the following lines.
We need to make sure this file is executed before any of our test files. That's a job for Bun's built-in [_preload_]() functionality. Create a `bunfig.toml` file in the root of your project (if it doesn't already exist) and add the following lines.
The `./happydom.ts` file should contain the registration code above.

View File

@@ -5,7 +5,7 @@ name: Set the system time in Bun's test runner
Bun's test runner supports setting the system time programmatically with the `setSystemTime` function.
```ts
import { test, expect, beforeAll, setSystemTime } from "bun:test";
import { test, expect, setSystemTime } from "bun:test";
test("party like it's 1999", () => {
const date = new Date("1999-01-01T00:00:00.000Z");

View File

@@ -2,12 +2,22 @@
name: Run your tests with the Bun test runner
---
Bun has a built-in test runner with a Jest-like `expect` API. To use it, run `bun test` from your project directory. The test runner will search for all files in the directory that match the following patterns:
Bun has a built-in [test runner](/docs/cli/test) with a Jest-like `expect` API.
- `*.test.{js|jsx|ts|tsx}`
- `*_test.{js|jsx|ts|tsx}`
- `*.spec.{js|jsx|ts|tsx}`
- `*_spec.{js|jsx|ts|tsx}`
---
To use it, run the `bun test` command from your project directory. The test runner will recursively search for all files in the directory that match the following patterns and execute the tests they contain.
```txt
*.test.{js|jsx|ts|tsx}
*_test.{js|jsx|ts|tsx}
*.spec.{js|jsx|ts|tsx}
*_spec.{js|jsx|ts|tsx}
```
---
Here's what the output of a typical test run looks like. In this case, there are three tests files (`test.test.js`, `test2.test.js`, and `test3.test.js`) containing two tests each (`add` and `multiply`).
```sh
$ bun test
@@ -51,17 +61,19 @@ Ran 2 tests across 1 files. [15.00ms]
---
All tests have a name, defined as the first parameter to the `test` function. Tests can also be inside a `describe` block.
All tests have a name, defined using the first parameter to the `test` function. Tests can also be grouped into suites with `describe`.
```ts
import { test, expect } from "bun:test";
test("add", () => {
expect(2 + 2).toEqual(4);
});
describe("math", () => {
test("add", () => {
expect(2 + 2).toEqual(4);
});
test("multiply", () => {
expect(2 * 2).toEqual(4);
test("multiply", () => {
expect(2 * 2).toEqual(4);
});
});
```
@@ -69,7 +81,7 @@ test("multiply", () => {
To filter which tests are executed by name, use the `-t`/`--test-name-pattern` flag.
Adding `-t add` will only run tests with "add" in the name. This flag also checks the name of the test suite (the first parameter to `describe`).
Adding `-t add` will only run tests with "add" in the name. This works with test names defined with `test` or test suite names defined with `describe`.
```sh
$ bun test -t add

View File

@@ -4,8 +4,10 @@ name: Skip tests with the Bun test runner
To skip a test with the Bun test runner, use the `test.skip` function.
```ts-diff
test.skip("unimplemented feature", ()=>{
```ts
import { test } from "bun:test";
test.skip("unimplemented feature", () => {
expect(Bun.isAwesome()).toBe(true);
});
```
@@ -34,4 +36,4 @@ Ran 3 tests across 1 files. [74.00ms]
See also:
- [Mark a test as a todo](/guides/test/todo-tests)
- [Docs > Test runner > Writing tests](/docs/test/writings-tests)
- [Docs > Test runner > Writing tests](/docs/test/writing)

View File

@@ -18,7 +18,7 @@ test("snapshot", () => {
---
The first time this test is executed, Bun will evaluate the value passed into `expect()` (`{ foo: "bar" }`) and write it to disk in a directory called `__snapshots__` that lives alongside the test file.
The first time this test is executed, Bun will evaluate the value passed into `expect()` and write it to disk in a directory called `__snapshots__` that lives alongside the test file. (Note the `snapshots: +1 added` line in the output.)
```sh
$ bun test test/snap
@@ -29,7 +29,7 @@ test/snap.test.ts:
1 pass
0 fail
snapshots: +1 added # note: the snapshot is created automatically the first run
snapshots: +1 added
1 expect() calls
Ran 1 tests across 1 files. [82.00ms]
```

View File

@@ -8,7 +8,7 @@ Use the `spyOn` utility to track method calls with Bun's test runner.
import { test, expect, spyOn } from "bun:test";
const leo = {
name: "Leonard",
name: "Leonardo",
sayHi(thing: string) {
console.log(`Sup I'm ${this.name} and I like ${thing}`);
},
@@ -27,7 +27,7 @@ Once the spy is created, it can be used to write `expect` assertions relating to
const leo = {
name: "Leonardo",
sayHi(thing: string) {
console.log(`Sup, I'm ${this.name} and I like ${thing}`);
console.log(`Sup I'm ${this.name} and I like ${thing}`);
},
};

View File

@@ -13,18 +13,6 @@ test.todo("unimplemented feature");
---
Optionally, you can provide a test implementation.
```ts
import { test, expect } from "bun:test";
test.todo("unimplemented feature", () => {
expect(Bun.isAwesome()).toBe(true);
});
```
---
The output of `bun test` indicates how many `todo` tests were encountered.
```sh
@@ -44,7 +32,19 @@ Ran 3 tests across 1 files. [74.00ms]
---
Note that `todo` tests _are executed_ by the test runner! They are _expected to fail_; if a todo test passes, the `bun test` run will return a non-zero exit code to signal the failure.
Optionally, you can provide a test implementation.
```ts
import { test, expect } from "bun:test";
test.todo("unimplemented feature", () => {
expect(Bun.isAwesome()).toBe(true);
});
```
---
If an implementation is provides, it will be executed and _expected to fail_ by test runner! If a todo test passes, the `bun test` run will return a non-zero exit code to signal the failure.
```sh
$ bun test
@@ -57,4 +57,4 @@ $ echo $?
See also:
- [Skip a test](/guides/test/skip-tests)
- [Docs > Test runner > Writing tests](/docs/test/writings-tests)
- [Docs > Test runner > Writing tests](/docs/test/writing)

View File

@@ -144,35 +144,131 @@ $ make assert-deps submodule npm-install-dev node-fallbacks runtime_js fallback_
Bun uses a series of make commands to rebuild parts of the codebase. The general rule for rebuilding is there is `make link` to rerun the linker, and then different make targets for different parts of the codebase. Do not pass `-j` to make as these scripts will break if run out of order, and multiple cores will be used when possible during the builds.
| What changed | Run this command |
| ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Zig Code | `make zig` |
| C++ Code | `make cpp` |
| Zig + C++ Code | `make dev` (combination of the above two) |
| JS/TS Code in `src/js` | `make js` (in bun-debug, js is loaded from disk without a recompile). If you change the names of any file or add/remove anything, you must also run `make dev`. |
| `*.classes.ts` | `make generate-classes dev` |
| JSSink | `make generate-sink cpp` |
| `src/node_fallbacks/*` | `make node-fallbacks zig` |
| `identifier_data.zig` | `make identifier-cache zig` |
| Code using `cppFn`/`JSC.markBinding` | `make headers` (TODO: explain explain what this is used for and why it's useful) |
{% table %}
- What changed
- Run this command
---
- Zig Code
- `make zig`
---
- C++ Code
- `make cpp`
---
- Zig + C++ Code
- `make dev` (combination of the above two)
---
- JS/TS Code in `src/js`
- `make js` (in bun-debug, js is loaded from disk without a recompile). If you change the names of any file or add/remove anything, you must also run `make dev`.
---
- `*.classes.ts`
- `make generate-classes dev`
---
- JSSink
- `make generate-sink cpp`
---
- `src/node_fallbacks/*`
- `make node-fallbacks zig`
---
- `identifier_data.zig`
- `make identifier-cache zig`
---
- Code using `cppFn`/`JSC.markBinding`
- `make headers` (TODO: explain explain what this is used for and why it's useful)
{% /table %}
`make setup` cloned a bunch of submodules and built the subprojects. When a submodule is out of date, run `make submodule` to quickly reset/update all your submodules, then you can rebuild individual submodules with their respective command.
| Dependency | Run this command |
| -------------- | ---------------------------------------- |
| WebKit | `bun install` (it is a prebuilt package) |
| uWebSockets | `make uws` |
| Mimalloc | `make mimalloc` |
| PicoHTTPParser | `make picohttp` |
| zlib | `make zlib` |
| BoringSSL | `make boringssl` |
| libarchive | `make libarchive` |
| lolhtml | `make lolhtml` |
| sqlite | `make sqlite` |
| TinyCC | `make tinycc` |
| c-ares | `make c-ares` |
| zstd | `make zstd` |
| Base64 | `make base64` |
{% table %}
- Dependency
- Run this command
---
- WebKit
- `bun install` (it is a prebuilt package)
---
- uWebSockets
- `make uws`
---
- Mimalloc
- `make mimalloc`
---
- PicoHTTPParser
- `make picohttp`
---
- zlib
- `make zlib`
---
- BoringSSL
- `make boringssl`
---
- libarchive
- `make libarchive`
---
- lolhtml
- `make lolhtml`
---
- sqlite
- `make sqlite`
---
- TinyCC
- `make tinycc`
---
- c-ares
- `make c-ares`
---
- zstd
- `make zstd`
---
- Base64
- `make base64`
{% /table %}
The above will probably also need Zig and/or C++ code rebuilt.
@@ -258,7 +354,7 @@ When these are changed, run:
$ make js
```
In debug builds, Bun automatically loads these from the filesystem, wherever it was compiled, so no need to re-run `make dev`. In release builds, this same behavior can be done via the environment variable `BUN_OVERRIDE_MODULE_PATH`. When set to the repository root, Bun will read from the bundled modules in the repository instead of the ones baked into the binary.
In debug builds, Bun automatically loads these from the filesystem, wherever it was compiled, so no need to re-run `make dev`.
## Release build

View File

@@ -93,6 +93,11 @@ Bun statically links these libraries:
- A fork of [`uWebsockets`](https://github.com/jarred-sumner/uwebsockets)
- Apache 2.0 licensed
---
- Parts of Tigerbeetle's IO code https://github.com/tigerbeetle/tigerbeetle/blob/532c8b70b9142c17e07737ab6d3da68d7500cbca/src/io/windows.zig#L1
- Apache 2.0 licensed
{% /table %}
## Polyfills

View File

@@ -37,7 +37,7 @@ logLevel = "debug" # "debug", "warn", "error"
# This will probably change in a future release to be just regular TOML instead. It is a holdover from the CLI argument parsing.
"process.env.bagel" = "'lox'"
[loaders]
[loader]
# When loading a .bagel file, run the JS parser
".bagel" = "js"
```

View File

@@ -102,55 +102,25 @@ Traditional file watchers like `nodemon` restart the entire process, so HTTP ser
Bun provides the following simplified API for implementing HTTP servers. Refer to [API > HTTP](/docs/api/http) for full details.
```ts#server.ts
import {type Serve} from "bun";
import {serve} from "bun";
globalThis.count ??= 0;
globalThis.count++;
export default {
serve({
fetch(req: Request) {
return new Response(`Reloaded ${globalThis.count} times`);
},
port: 3000,
} satisfies Serve;
});
```
The file above is simply exporting an object with a `fetch` handler defined. When this file is executed, Bun interprets this as an HTTP server and passes the exported object into `Bun.serve`.
Unlike an explicit call to `Bun.serve`, the object-based syntax works out of the box with `bun --hot`. When you save the file, your HTTP server be reloaded with the updated code without the process being restarted. This results in seriously fast refresh speeds.
When you save the file, your HTTP server be reloaded with the updated code without the process being restarted. This results in seriously fast refresh speeds.
{% image src="https://user-images.githubusercontent.com/709451/195477632-5fd8a73e-014d-4589-9ba2-e075ad9eb040.gif" alt="Bun vs Nodemon refresh speeds" caption="Bun on the left, Nodemon on the right." /%}
For more fine-grained control, you can use the `Bun.serve` API directly and handle the server reloading manually.
```ts#server.ts
import type {Serve, Server} from "bun";
// make TypeScript happy
declare global {
var count: number;
var server: Server;
}
globalThis.count ??= 0;
globalThis.count++;
// define server parameters
const serverOptions: Serve = {
port: 3000,
fetch(req) {
return new Response(`Reloaded ${globalThis.count} times`);
}
};
if (!globalThis.server) {
globalThis.server = Bun.serve(serverOptions);
} else {
// reload server
globalThis.server.reload(serverOptions);
}
```
{% callout %}
**Note** — In a future version of Bun, support for Vite's `import.meta.hot` is planned to enable better lifecycle management for hot reloading and to align with the ecosystem.

View File

@@ -38,7 +38,7 @@ const styles = css`
}
`;
export default {
Bun.serve({
websocket: {
message(ws, msg) {
ws.send(styles);
@@ -86,4 +86,4 @@ export default {
},
);
},
};
});

File diff suppressed because it is too large Load Diff

View File

@@ -1,271 +0,0 @@
import type { DAP } from "../protocol";
const capabilities: DAP.Capabilities = {
/**
* The debug adapter supports the `configurationDone` request.
* @see configurationDone
*/
supportsConfigurationDoneRequest: true,
/**
* The debug adapter supports function breakpoints using the `setFunctionBreakpoints` request.
* @see setFunctionBreakpoints
*/
supportsFunctionBreakpoints: true,
/**
* The debug adapter supports conditional breakpoints.
* @see setBreakpoints
* @see setInstructionBreakpoints
* @see setFunctionBreakpoints
* @see setExceptionBreakpoints
* @see setDataBreakpoints
*/
supportsConditionalBreakpoints: true,
/**
* The debug adapter supports breakpoints that break execution after a specified number of hits.
* @see setBreakpoints
* @see setInstructionBreakpoints
* @see setFunctionBreakpoints
* @see setExceptionBreakpoints
* @see setDataBreakpoints
*/
supportsHitConditionalBreakpoints: true,
/**
* The debug adapter supports a (side effect free) `evaluate` request for data hovers.
* @see evaluate
*/
supportsEvaluateForHovers: true,
/**
* Available exception filter options for the `setExceptionBreakpoints` request.
* @see setExceptionBreakpoints
*/
exceptionBreakpointFilters: [
{
filter: "all",
label: "Caught Exceptions",
default: false,
supportsCondition: true,
description: "Breaks on all throw errors, even if they're caught later.",
conditionDescription: `error.name == "CustomError"`,
},
{
filter: "uncaught",
label: "Uncaught Exceptions",
default: false,
supportsCondition: true,
description: "Breaks only on errors or promise rejections that are not handled.",
conditionDescription: `error.name == "CustomError"`,
},
],
/**
* The debug adapter supports stepping back via the `stepBack` and `reverseContinue` requests.
* @see stepBack
* @see reverseContinue
*/
supportsStepBack: false,
/**
* The debug adapter supports setting a variable to a value.
* @see setVariable
*/
supportsSetVariable: false,
/**
* The debug adapter supports restarting a frame.
* @see restartFrame
*/
supportsRestartFrame: false,
/**
* The debug adapter supports the `gotoTargets` request.
* @see gotoTargets
*/
supportsGotoTargetsRequest: false,
/**
* The debug adapter supports the `stepInTargets` request.
* @see stepInTargets
*/
supportsStepInTargetsRequest: false,
/**
* The debug adapter supports the `completions` request.
* @see completions
*/
supportsCompletionsRequest: false,
/**
* The set of characters that should trigger completion in a REPL.
* If not specified, the UI should assume the `.` character.
* @see completions
*/
completionTriggerCharacters: [".", "[", '"', "'"],
/**
* The debug adapter supports the `modules` request.
* @see modules
*/
supportsModulesRequest: false,
/**
* The set of additional module information exposed by the debug adapter.
* @see modules
*/
additionalModuleColumns: [],
/**
* Checksum algorithms supported by the debug adapter.
*/
supportedChecksumAlgorithms: [],
/**
* The debug adapter supports the `restart` request.
* In this case a client should not implement `restart` by terminating
* and relaunching the adapter but by calling the `restart` request.
* @see restart
*/
supportsRestartRequest: false,
/**
* The debug adapter supports `exceptionOptions` on the `setExceptionBreakpoints` request.
* @see setExceptionBreakpoints
*/
supportsExceptionOptions: false,
/**
* The debug adapter supports a `format` attribute on the `stackTrace`, `variables`, and `evaluate` requests.
* @see stackTrace
* @see variables
* @see evaluate
*/
supportsValueFormattingOptions: false,
/**
* The debug adapter supports the `exceptionInfo` request.
* @see exceptionInfo
*/
supportsExceptionInfoRequest: true,
/**
* The debug adapter supports the `terminateDebuggee` attribute on the `disconnect` request.
* @see disconnect
*/
supportTerminateDebuggee: true,
/**
* The debug adapter supports the `suspendDebuggee` attribute on the `disconnect` request.
* @see disconnect
*/
supportSuspendDebuggee: false,
/**
* The debug adapter supports the delayed loading of parts of the stack,
* which requires that both the `startFrame` and `levels` arguments and
* the `totalFrames` result of the `stackTrace` request are supported.
* @see stackTrace
*/
supportsDelayedStackTraceLoading: true,
/**
* The debug adapter supports the `loadedSources` request.
* @see loadedSources
*/
supportsLoadedSourcesRequest: true,
/**
* The debug adapter supports log points by interpreting the `logMessage` attribute of the `SourceBreakpoint`.
* @see setBreakpoints
*/
supportsLogPoints: true,
/**
* The debug adapter supports the `terminateThreads` request.
* @see terminateThreads
*/
supportsTerminateThreadsRequest: false,
/**
* The debug adapter supports the `setExpression` request.
* @see setExpression
*/
supportsSetExpression: false,
/**
* The debug adapter supports the `terminate` request.
* @see terminate
*/
supportsTerminateRequest: true,
/**
* The debug adapter supports data breakpoints.
* @see setDataBreakpoints
*/
supportsDataBreakpoints: false,
/**
* The debug adapter supports the `readMemory` request.
* @see readMemory
*/
supportsReadMemoryRequest: false,
/**
* The debug adapter supports the `writeMemory` request.
* @see writeMemory
*/
supportsWriteMemoryRequest: false,
/**
* The debug adapter supports the `disassemble` request.
* @see disassemble
*/
supportsDisassembleRequest: false,
/**
* The debug adapter supports the `cancel` request.
* @see cancel
*/
supportsCancelRequest: false,
/**
* The debug adapter supports the `breakpointLocations` request.
* @see breakpointLocations
*/
supportsBreakpointLocationsRequest: true,
/**
* The debug adapter supports the `clipboard` context value in the `evaluate` request.
* @see evaluate
*/
supportsClipboardContext: false,
/**
* The debug adapter supports stepping granularities (argument `granularity`) for the stepping requests.
* @see stepIn
*/
supportsSteppingGranularity: false,
/**
* The debug adapter supports adding breakpoints based on instruction references.
* @see setInstructionBreakpoints
*/
supportsInstructionBreakpoints: false,
/**
* The debug adapter supports `filterOptions` as an argument on the `setExceptionBreakpoints` request.
* @see setExceptionBreakpoints
*/
supportsExceptionFilterOptions: true,
/**
* The debug adapter supports the `singleThread` property on the execution requests
* (`continue`, `next`, `stepIn`, `stepOut`, `reverseContinue`, `stepBack`).
*/
supportsSingleThreadExecutionRequests: false,
};
export default capabilities;

View File

@@ -21,7 +21,7 @@ export class UnixSignal extends EventEmitter<UnixSignalEventMap> {
#server: Server;
#ready: Promise<void>;
constructor(path?: string) {
constructor(path?: string | URL) {
super();
this.#path = path ? parseUnixPath(path) : randomUnixPath();
this.#server = createServer();
@@ -70,12 +70,12 @@ export class UnixSignal extends EventEmitter<UnixSignalEventMap> {
}
}
function randomUnixPath(): string {
export function randomUnixPath(): string {
return join(tmpdir(), `${Math.random().toString(36).slice(2)}.sock`);
}
function parseUnixPath(path: string): string {
if (path.startsWith("/")) {
function parseUnixPath(path: string | URL): string {
if (typeof path === "string" && path.startsWith("/")) {
return path;
}
try {

View File

@@ -80,7 +80,7 @@ class ActualSourceMap implements SourceMap {
const { line: gline, column: gcolumn } = lineRange;
return {
line: lineToLine(gline),
line: lineTo0BasedLine(gline),
column: columnToColumn(gcolumn),
verified: true,
};
@@ -144,9 +144,17 @@ class NoopSourceMap implements SourceMap {
const defaultSourceMap = new NoopSourceMap();
export function SourceMap(url?: string): SourceMap {
if (!url || !url.startsWith("data:")) {
if (!url) {
return defaultSourceMap;
}
if (!url.startsWith("data:")) {
const match = url.match(/\/\/[#@]\s*sourceMappingURL=(.*)$/m);
if (!match) {
return defaultSourceMap;
}
const [_, sourceMapUrl] = match;
url = sourceMapUrl;
}
try {
const [_, base64] = url.split(",", 2);
const decoded = Buffer.from(base64, "base64url").toString("utf8");

View File

@@ -52,10 +52,10 @@ export class WebSocketInspector extends EventEmitter<InspectorEventMap> implemen
// @ts-expect-error: Support both Bun and Node.js version of `headers`.
webSocket = new WebSocket(url, {
headers: {
"Ref-Event-Loop": "1",
"Ref-Event-Loop": "0",
},
finishRequest: (request: import("http").ClientRequest) => {
request.setHeader("Ref-Event-Loop", "1");
request.setHeader("Ref-Event-Loop", "0");
request.end();
},
});
@@ -67,18 +67,23 @@ export class WebSocketInspector extends EventEmitter<InspectorEventMap> implemen
webSocket.addEventListener("open", () => {
this.emit("Inspector.connected");
for (const request of this.#pendingRequests) {
for (let i = 0; i < this.#pendingRequests.length; i++) {
const request = this.#pendingRequests[i];
if (this.#send(request)) {
this.emit("Inspector.request", request);
} else {
this.#pendingRequests = this.#pendingRequests.slice(i);
break;
}
}
this.#pendingRequests.length = 0;
});
webSocket.addEventListener("message", ({ data }) => {
if (typeof data === "string") {
this.#accept(data);
} else {
this.emit("Inspector.error", new Error(`WebSocket received unexpected binary message: ${data.toString()}`));
}
});
@@ -125,8 +130,12 @@ export class WebSocketInspector extends EventEmitter<InspectorEventMap> implemen
};
return new Promise((resolve, reject) => {
let timerId: number | undefined;
const done = (result: any) => {
this.#pendingResponses.delete(id);
if (timerId) {
clearTimeout(timerId);
}
if (result instanceof Error) {
reject(result);
} else {
@@ -136,6 +145,7 @@ export class WebSocketInspector extends EventEmitter<InspectorEventMap> implemen
this.#pendingResponses.set(id, done);
if (this.#send(request)) {
timerId = +setTimeout(() => done(new Error(`Timed out: ${method}`)), 10_000);
this.emit("Inspector.request", request);
} else {
this.emit("Inspector.pendingRequest", request);
@@ -183,7 +193,6 @@ export class WebSocketInspector extends EventEmitter<InspectorEventMap> implemen
return;
}
this.#pendingResponses.delete(id);
if ("error" in data) {
const { error } = data;
const { message } = error;
@@ -218,6 +227,7 @@ export class WebSocketInspector extends EventEmitter<InspectorEventMap> implemen
resolve(error ?? new Error("WebSocket closed"));
}
this.#pendingResponses.clear();
if (error) {
this.emit("Inspector.error", error);
}

View File

@@ -1,34 +1,34 @@
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
// Without an ESM loader, this polyfill is impossible to apply automatically,
// due to the per-module nature of import.meta. In order to use this polyfill,
// you must import it in every module that uses import.meta, and call it with
// the import.meta object as the argument. When the polyfills are integrated
// with bun build, this could be done automatically by the build process at
// the top of every module file bundled.
export default function polyfillImportMeta(metaIn: ImportMeta) {
const require2 = createRequire(metaIn.url);
const metapath = fileURLToPath(metaIn.url);
const meta: ImportMeta = {
url: metaIn.url,
main: metapath === process.argv[1],
path: metapath,
dir: path.dirname(metapath),
file: path.basename(metapath),
require: require2,
async resolve(id: string, parent?: string) {
return this.resolveSync(id, parent);
},
resolveSync(id: string, parent?: string) {
return require2.resolve(id, {
paths: typeof parent === 'string' ? [
path.resolve(parent.startsWith('file://') ? fileURLToPath(parent) : parent, '..')
] : undefined,
});
},
};
Object.assign(metaIn, meta);
}
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { createRequire } from 'node:module';
// Without an ESM loader, this polyfill is impossible to apply automatically,
// due to the per-module nature of import.meta. In order to use this polyfill,
// you must import it in every module that uses import.meta, and call it with
// the import.meta object as the argument. When the polyfills are integrated
// with bun build, this could be done automatically by the build process at
// the top of every module file bundled.
export default function polyfillImportMeta(metaIn: ImportMeta) {
const require2 = createRequire(metaIn.url);
const metapath = fileURLToPath(metaIn.url);
const meta: ImportMeta = {
url: metaIn.url,
main: metapath === process.argv[1],
path: metapath,
dir: path.dirname(metapath),
file: path.basename(metapath),
require: require2,
async resolve(id: string, parent?: string) {
return this.resolveSync(id, parent);
},
resolveSync(id: string, parent?: string) {
return require2.resolve(id, {
paths: typeof parent === 'string' ? [
path.resolve(parent.startsWith('file://') ? fileURLToPath(parent) : parent, '..')
] : undefined,
});
},
};
Object.assign(metaIn, meta);
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,67 +1,67 @@
type BunArrayBufferSink = InstanceType<typeof Bun.ArrayBufferSink>;
export class ArrayBufferSink implements BunArrayBufferSink {
#started: boolean = true;
#closed: boolean = false;
#offset: number = 0;
#stream: boolean = false;
#asUint8: boolean = false;
#buffer: Buffer = Buffer.allocUnsafe(8192);
get sinkId(): number { return 0; } //? undocumented, seems to always return 0
#ASSERT_NOT_CLOSED(caller: AnyFunction): void {
if (!this.#closed) return;
const err = new TypeError('Expected Sink');
Error.captureStackTrace(err, caller);
throw err;
}
start({ asUint8Array = false, highWaterMark = 8192, stream = false }: Parameters<BunArrayBufferSink['start']>[0] = {}): void {
this.#ASSERT_NOT_CLOSED(this.start);
this.#started = true;
this.#offset = 0;
this.#stream = stream;
this.#asUint8 = asUint8Array;
if (highWaterMark !== this.#buffer.byteLength) this.#buffer = Buffer.allocUnsafe(highWaterMark);
}
write(data: string | ArrayBufferView | SharedArrayBuffer | ArrayBuffer): number {
this.#ASSERT_NOT_CLOSED(this.write);
if (typeof data === 'string') data = new TextEncoder().encode(data);
const writedata = (data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
// this is very bad API design to not throw an error here, but it's what Bun does
if (!this.#started) return writedata.byteLength;
if (this.#offset + writedata.byteLength > this.#buffer.byteLength) {
const newLength = Math.ceil((this.#offset + writedata.byteLength) / 1024) * 1024;
const newBuffer = Buffer.allocUnsafe(newLength);
newBuffer.set(this.#buffer);
this.#buffer = newBuffer;
}
this.#buffer.set(writedata, this.#offset);
this.#offset += writedata.byteLength;
return writedata.byteLength;
}
flush(): number | Uint8Array | ArrayBuffer {
this.#ASSERT_NOT_CLOSED(this.flush);
if (!this.#stream) return 0; //! brokenly seems to always return 0 and do nothing
const flushed = new Uint8Array(this.#offset);
flushed.set(this.#buffer.subarray(0, this.#offset)); // faster than Buffer.copy or Uint8Array.slice
this.#offset = 0;
return this.#asUint8 ? flushed : flushed.buffer as ArrayBuffer;
}
end(): Uint8Array | ArrayBuffer {
this.#ASSERT_NOT_CLOSED(this.end);
const stream = this.#stream;
this.#stream = true; // force flush() to return the data
const buffer = this.flush() as Uint8Array | ArrayBuffer;
this.#stream = stream;
this.#started = false;
return buffer;
}
close(): void { this.#closed = true; } //? undocumented
}
type BunArrayBufferSink = InstanceType<typeof Bun.ArrayBufferSink>;
export class ArrayBufferSink implements BunArrayBufferSink {
#started: boolean = true;
#closed: boolean = false;
#offset: number = 0;
#stream: boolean = false;
#asUint8: boolean = false;
#buffer: Buffer = Buffer.allocUnsafe(8192);
get sinkId(): number { return 0; } //? undocumented, seems to always return 0
#ASSERT_NOT_CLOSED(caller: AnyFunction): void {
if (!this.#closed) return;
const err = new TypeError('Expected Sink');
Error.captureStackTrace(err, caller);
throw err;
}
start({ asUint8Array = false, highWaterMark = 8192, stream = false }: Parameters<BunArrayBufferSink['start']>[0] = {}): void {
this.#ASSERT_NOT_CLOSED(this.start);
this.#started = true;
this.#offset = 0;
this.#stream = stream;
this.#asUint8 = asUint8Array;
if (highWaterMark !== this.#buffer.byteLength) this.#buffer = Buffer.allocUnsafe(highWaterMark);
}
write(data: string | ArrayBufferView | SharedArrayBuffer | ArrayBuffer): number {
this.#ASSERT_NOT_CLOSED(this.write);
if (typeof data === 'string') data = new TextEncoder().encode(data);
const writedata = (data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
// this is very bad API design to not throw an error here, but it's what Bun does
if (!this.#started) return writedata.byteLength;
if (this.#offset + writedata.byteLength > this.#buffer.byteLength) {
const newLength = Math.ceil((this.#offset + writedata.byteLength) / 1024) * 1024;
const newBuffer = Buffer.allocUnsafe(newLength);
newBuffer.set(this.#buffer);
this.#buffer = newBuffer;
}
this.#buffer.set(writedata, this.#offset);
this.#offset += writedata.byteLength;
return writedata.byteLength;
}
flush(): number | Uint8Array | ArrayBuffer {
this.#ASSERT_NOT_CLOSED(this.flush);
if (!this.#stream) return 0; //! brokenly seems to always return 0 and do nothing
const flushed = new Uint8Array(this.#offset);
flushed.set(this.#buffer.subarray(0, this.#offset)); // faster than Buffer.copy or Uint8Array.slice
this.#offset = 0;
return this.#asUint8 ? flushed : flushed.buffer as ArrayBuffer;
}
end(): Uint8Array | ArrayBuffer {
this.#ASSERT_NOT_CLOSED(this.end);
const stream = this.#stream;
this.#stream = true; // force flush() to return the data
const buffer = this.flush() as Uint8Array | ArrayBuffer;
this.#stream = stream;
this.#started = false;
return buffer;
}
close(): void { this.#closed = true; } //? undocumented
}

View File

@@ -1,21 +1,21 @@
import dns from 'node:dns';
const dnsObj: typeof Bun.dns = {
async lookup(hostname, options) {
const opts = { verbatim: true, all: true } as dns.LookupOptions;
if (options?.family) {
if (options.family === 'IPv4') opts.family = 4;
else if (options.family === 'IPv6') opts.family = 6;
else if (options.family === 'any') opts.family = 0;
else opts.family = options.family;
}
if (options?.flags) opts.hints = options.flags;
const records = ((await dns.promises.resolveAny(hostname))
.filter(r => r.type === 'A' || r.type === 'AAAA') as (dns.AnyARecord | dns.AnyAaaaRecord)[])
.map(r => ({ address: r.address, family: r.type === 'A' ? 4 as const : 6 as const, ttl: r.ttl }));
return records;
},
// This has more properties but they're not documented on bun-types yet, oh well.
};
export default dnsObj;
import dns from 'node:dns';
const dnsObj: typeof Bun.dns = {
async lookup(hostname, options) {
const opts = { verbatim: true, all: true } as dns.LookupOptions;
if (options?.family) {
if (options.family === 'IPv4') opts.family = 4;
else if (options.family === 'IPv6') opts.family = 6;
else if (options.family === 'any') opts.family = 0;
else opts.family = options.family;
}
if (options?.flags) opts.hints = options.flags;
const records = ((await dns.promises.resolveAny(hostname))
.filter(r => r.type === 'A' || r.type === 'AAAA') as (dns.AnyARecord | dns.AnyAaaaRecord)[])
.map(r => ({ address: r.address, family: r.type === 'A' ? 4 as const : 6 as const, ttl: r.ttl }));
return records;
},
// This has more properties but they're not documented on bun-types yet, oh well.
};
export default dnsObj;

View File

@@ -1,195 +1,195 @@
import fs from 'node:fs';
import tty from 'node:tty';
import streams from 'node:stream';
import { ReadableStream as NodeWebReadableStream } from 'node:stream/web';
import { FileSink } from './filesink.js';
import { SystemError } from '../../utils/errors.js';
import type { FileBlob as BunFileBlob, FileSink as BunFileSink } from 'bun';
type NodeJSStream = streams.Readable | streams.Writable;
function NodeJSReadableStreamToBlob(stream: NodeJS.ReadableStream | NodeJS.ReadWriteStream, iostream: boolean = false, type?: string): Promise<Blob> {
if (stream.isPaused()) stream.resume();
return new Promise((resolve, reject) => {
const chunks: any[] = [];
const dataHandler = (chunk: any) => { chunks.push(chunk); if (iostream) end(); };
const end = () => {
resolve(new Blob(chunks, type != null ? { type } : undefined));
stream.off('data', dataHandler);
stream.off('end', end);
stream.pause();
};
stream.once('data', dataHandler).once('end', end);
//.once('error', reject); Bun waits to error on actual operations on the stream, therefore so will we.
});
}
export const NodeJSStreamFileBlob = class FileBlob extends Blob {
constructor(source: NodeJSStream, slice: [number?, number?] = [undefined, undefined], type = 'application/octet-stream') {
super(undefined, { type });
Reflect.deleteProperty(this, 'size');
if (source === process.stdout || source === process.stdin || source === process.stderr) {
this.#iostream = true;
}
this.#readable = source instanceof streams.Readable && !(source instanceof tty.WriteStream);
this.#source = source;
this.#slice = slice;
this.#size = Infinity;
}
readonly #iostream: boolean = false;
readonly #readable: boolean;
readonly #source: NodeJSStream;
readonly #slice: [number?, number?];
#size: number;
slice(begin?: number, end?: number, contentType?: string): Blob;
slice(begin?: number, contentType?: string): Blob;
slice(contentType?: string): Blob;
slice(beginOrType?: number | string, endOrType?: number | string, contentType: string = this.type): Blob {
if (typeof beginOrType === 'string') return new FileBlob(this.#source, this.#slice, beginOrType);
if (typeof endOrType === 'string') return new FileBlob(this.#source, [beginOrType, undefined], endOrType);
return new FileBlob(this.#source, [beginOrType, endOrType], contentType);
}
override stream(): ReadableStream<Uint8Array> {
// This makes no sense but Bun does it so we will too
if (!this.#readable) return new ReadableStream();
return streams.Readable.toWeb(this.#source as streams.Readable);
}
#blobStackFn: AnyFunction = this.#getBlob;
async #getBlob(): Promise<Blob> {
if (!this.#readable) {
const err = new SystemError(-1, 'read');
Error.captureStackTrace(err, this.#blobStackFn);
throw err;
}
const blob = (await NodeJSReadableStreamToBlob(this.#source as streams.Readable, this.#iostream)).slice(...this.#slice);
this.#size = blob.size;
return blob;
}
override async text(): Promise<string> {
if (this.#blobStackFn !== this.json) this.#blobStackFn = this.text;
return (await this.#getBlob()).text();
}
override async arrayBuffer(): Promise<ArrayBuffer> {
this.#blobStackFn = this.arrayBuffer;
return (await this.#getBlob()).arrayBuffer();
}
override async json<TJSONReturnType = unknown>(): Promise<TJSONReturnType> {
this.#blobStackFn = this.json;
return JSON.parse(await this.text()) as Promise<TJSONReturnType>;
}
override get size(): number { return this.#size; }
override set size(_) { return; }
};
export class FileBlob extends Blob implements BunFileBlob {
constructor(fdOrPath: number | string, opts: BlobPropertyBag = {}) {
opts.type ??= 'application/octet-stream'; // TODO: Get MIME type from file extension
super(undefined, opts);
Reflect.deleteProperty(this, 'size');
if (Reflect.get(opts, '__data')) this.#data = Reflect.get(opts, '__data') as Blob;
const slice = Reflect.get(opts, '__slice') as [number?, number?] | undefined;
if (slice) {
slice[0] &&= slice[0] | 0; // int cast
slice[1] &&= slice[1] | 0; // int cast
this.#slice = slice;
slice[0] ??= 0;
if (typeof slice[1] === 'undefined') {
if (slice[0] < 0) this.#sliceSize = -slice[0];
}
else if (slice[0] < 0 && slice[1] < 0) this.#sliceSize = -(slice[0] - slice[1]);
else if (slice[0] >= 0 && slice[1] >= 0) this.#sliceSize = slice[1] - slice[0];
}
if (typeof fdOrPath === 'string') try {
this.#fd = fs.openSync(fdOrPath, 'r+');
} catch (err) {
this.#error = err as SystemError;
}
else {
this.#fd = fdOrPath;
this.#error = Reflect.get(opts, '__error') as SystemError | undefined;
}
if (!this.#error) {
const rstream = fs.createReadStream('', { fd: this.#fd, start: this.#slice[0], end: this.#slice[1] });
this.#readable = streams.Readable.toWeb(rstream);
}
}
readonly #readable?: NodeWebReadableStream;
readonly #error?: SystemError;
readonly #slice: [number?, number?] = [];
readonly #sliceSize: number = 0;
readonly #fd: number = NaN;
#data?: Blob;
#read() {
if (this.#error) throw this.#error;
const read = fs.readFileSync(this.#fd);
this.#data = new Blob([read.subarray(...this.#slice)], { type: this.type });
}
//! Bun 0.2 seems to return undefined for this, this might not be accurate or it's broken on Bun's side
get readable(): ReadableStream<any> {
if (this.#error) throw this.#error;
return this.#readable! as ReadableStream;
}
get lastModified(): number {
if (this.#error) throw this.#error;
return fs.fstatSync(this.#fd).mtimeMs;
}
async exists(): Promise<boolean> {
return !this.#error;
}
writer(): BunFileSink {
if (this.#error) throw this.#error;
return new FileSink(this.#fd);
}
// TODO: what's contentType?
override slice(begin?: number | string, end?: number | string, contentType?: string): FileBlob {
if (typeof begin === 'string') {
contentType = begin;
begin = undefined;
}
if (typeof end === 'string') {
contentType = end;
end = undefined;
}
return new FileBlob(this.#fd, {
__error: this.#error,
__slice: [begin, end],
__data: this.#data?.slice(begin, end),
} as BlobPropertyBag);
}
override arrayBuffer(): Promise<ArrayBuffer> {
if (!this.#data) this.#read();
return new Blob([this.#data ?? '']).arrayBuffer();
}
override text(): Promise<string> {
if (!this.#data) this.#read();
return new Blob([this.#data ?? '']).text();
}
override json(): Promise<any>;
override json<TJSONReturnType = unknown>(): Promise<TJSONReturnType>;
override json<TJSONReturnType = unknown>(): Promise<TJSONReturnType> | Promise<any> {
if (!this.#data) this.#read();
return new Blob([this.#data ?? '']).json();
}
override stream(): NodeJS.ReadableStream;
override stream(): ReadableStream<Uint8Array>;
override stream(): ReadableStream<Uint8Array> | NodeJS.ReadableStream {
if (!this.#data) this.#read();
return new Blob([this.#data ?? '']).stream();
}
override get size(): number {
return this.#data?.size ?? (this.#sliceSize || 0);
}
}
import fs from 'node:fs';
import tty from 'node:tty';
import streams from 'node:stream';
import { ReadableStream as NodeWebReadableStream } from 'node:stream/web';
import { FileSink } from './filesink.js';
import { SystemError } from '../../utils/errors.js';
import type { FileBlob as BunFileBlob, FileSink as BunFileSink } from 'bun';
type NodeJSStream = streams.Readable | streams.Writable;
function NodeJSReadableStreamToBlob(stream: NodeJS.ReadableStream | NodeJS.ReadWriteStream, iostream: boolean = false, type?: string): Promise<Blob> {
if (stream.isPaused()) stream.resume();
return new Promise((resolve, reject) => {
const chunks: any[] = [];
const dataHandler = (chunk: any) => { chunks.push(chunk); if (iostream) end(); };
const end = () => {
resolve(new Blob(chunks, type != null ? { type } : undefined));
stream.off('data', dataHandler);
stream.off('end', end);
stream.pause();
};
stream.once('data', dataHandler).once('end', end);
//.once('error', reject); Bun waits to error on actual operations on the stream, therefore so will we.
});
}
export const NodeJSStreamFileBlob = class FileBlob extends Blob {
constructor(source: NodeJSStream, slice: [number?, number?] = [undefined, undefined], type = 'application/octet-stream') {
super(undefined, { type });
Reflect.deleteProperty(this, 'size');
if (source === process.stdout || source === process.stdin || source === process.stderr) {
this.#iostream = true;
}
this.#readable = source instanceof streams.Readable && !(source instanceof tty.WriteStream);
this.#source = source;
this.#slice = slice;
this.#size = Infinity;
}
readonly #iostream: boolean = false;
readonly #readable: boolean;
readonly #source: NodeJSStream;
readonly #slice: [number?, number?];
#size: number;
slice(begin?: number, end?: number, contentType?: string): Blob;
slice(begin?: number, contentType?: string): Blob;
slice(contentType?: string): Blob;
slice(beginOrType?: number | string, endOrType?: number | string, contentType: string = this.type): Blob {
if (typeof beginOrType === 'string') return new FileBlob(this.#source, this.#slice, beginOrType);
if (typeof endOrType === 'string') return new FileBlob(this.#source, [beginOrType, undefined], endOrType);
return new FileBlob(this.#source, [beginOrType, endOrType], contentType);
}
override stream(): ReadableStream<Uint8Array> {
// This makes no sense but Bun does it so we will too
if (!this.#readable) return new ReadableStream();
return streams.Readable.toWeb(this.#source as streams.Readable);
}
#blobStackFn: AnyFunction = this.#getBlob;
async #getBlob(): Promise<Blob> {
if (!this.#readable) {
const err = new SystemError(-1, 'read');
Error.captureStackTrace(err, this.#blobStackFn);
throw err;
}
const blob = (await NodeJSReadableStreamToBlob(this.#source as streams.Readable, this.#iostream)).slice(...this.#slice);
this.#size = blob.size;
return blob;
}
override async text(): Promise<string> {
if (this.#blobStackFn !== this.json) this.#blobStackFn = this.text;
return (await this.#getBlob()).text();
}
override async arrayBuffer(): Promise<ArrayBuffer> {
this.#blobStackFn = this.arrayBuffer;
return (await this.#getBlob()).arrayBuffer();
}
override async json<TJSONReturnType = unknown>(): Promise<TJSONReturnType> {
this.#blobStackFn = this.json;
return JSON.parse(await this.text()) as Promise<TJSONReturnType>;
}
override get size(): number { return this.#size; }
override set size(_) { return; }
};
export class FileBlob extends Blob implements BunFileBlob {
constructor(fdOrPath: number | string, opts: BlobPropertyBag = {}) {
opts.type ??= 'application/octet-stream'; // TODO: Get MIME type from file extension
super(undefined, opts);
Reflect.deleteProperty(this, 'size');
if (Reflect.get(opts, '__data')) this.#data = Reflect.get(opts, '__data') as Blob;
const slice = Reflect.get(opts, '__slice') as [number?, number?] | undefined;
if (slice) {
slice[0] &&= slice[0] | 0; // int cast
slice[1] &&= slice[1] | 0; // int cast
this.#slice = slice;
slice[0] ??= 0;
if (typeof slice[1] === 'undefined') {
if (slice[0] < 0) this.#sliceSize = -slice[0];
}
else if (slice[0] < 0 && slice[1] < 0) this.#sliceSize = -(slice[0] - slice[1]);
else if (slice[0] >= 0 && slice[1] >= 0) this.#sliceSize = slice[1] - slice[0];
}
if (typeof fdOrPath === 'string') try {
this.#fd = fs.openSync(fdOrPath, 'r+');
} catch (err) {
this.#error = err as SystemError;
}
else {
this.#fd = fdOrPath;
this.#error = Reflect.get(opts, '__error') as SystemError | undefined;
}
if (!this.#error) {
const rstream = fs.createReadStream('', { fd: this.#fd, start: this.#slice[0], end: this.#slice[1] });
this.#readable = streams.Readable.toWeb(rstream);
}
}
readonly #readable?: NodeWebReadableStream;
readonly #error?: SystemError;
readonly #slice: [number?, number?] = [];
readonly #sliceSize: number = 0;
readonly #fd: number = NaN;
#data?: Blob;
#read() {
if (this.#error) throw this.#error;
const read = fs.readFileSync(this.#fd);
this.#data = new Blob([read.subarray(...this.#slice)], { type: this.type });
}
//! Bun 0.2 seems to return undefined for this, this might not be accurate or it's broken on Bun's side
get readable(): ReadableStream<any> {
if (this.#error) throw this.#error;
return this.#readable! as ReadableStream;
}
get lastModified(): number {
if (this.#error) throw this.#error;
return fs.fstatSync(this.#fd).mtimeMs;
}
async exists(): Promise<boolean> {
return !this.#error;
}
writer(): BunFileSink {
if (this.#error) throw this.#error;
return new FileSink(this.#fd);
}
// TODO: what's contentType?
override slice(begin?: number | string, end?: number | string, contentType?: string): FileBlob {
if (typeof begin === 'string') {
contentType = begin;
begin = undefined;
}
if (typeof end === 'string') {
contentType = end;
end = undefined;
}
return new FileBlob(this.#fd, {
__error: this.#error,
__slice: [begin, end],
__data: this.#data?.slice(begin, end),
} as BlobPropertyBag);
}
override arrayBuffer(): Promise<ArrayBuffer> {
if (!this.#data) this.#read();
return new Blob([this.#data ?? '']).arrayBuffer();
}
override text(): Promise<string> {
if (!this.#data) this.#read();
return new Blob([this.#data ?? '']).text();
}
override json(): Promise<any>;
override json<TJSONReturnType = unknown>(): Promise<TJSONReturnType>;
override json<TJSONReturnType = unknown>(): Promise<TJSONReturnType> | Promise<any> {
if (!this.#data) this.#read();
return new Blob([this.#data ?? '']).json();
}
override stream(): NodeJS.ReadableStream;
override stream(): ReadableStream<Uint8Array>;
override stream(): ReadableStream<Uint8Array> | NodeJS.ReadableStream {
if (!this.#data) this.#read();
return new Blob([this.#data ?? '']).stream();
}
override get size(): number {
return this.#data?.size ?? (this.#sliceSize || 0);
}
}

View File

@@ -1,87 +1,87 @@
import fs from 'node:fs';
import { SystemError } from '../../utils/errors.js';
import type { FileSink as BunFileSink } from 'bun';
export class FileSink implements BunFileSink {
constructor(fdOrPathOrStream: number | string | NodeJS.WritableStream) {
if (typeof fdOrPathOrStream === 'string') try {
this.#fd = fs.openSync(fdOrPathOrStream, 'a+');
fs.ftruncateSync(this.#fd, 0);
} catch (err) {
throw err as SystemError;
}
else if (typeof fdOrPathOrStream === 'number') {
this.#fd = fdOrPathOrStream; // hope this fd is writable
fs.ftruncateSync(this.#fd, 0);
}
else {
this.#stream = fdOrPathOrStream;
}
}
#fd: number = NaN;
#stream: NodeJS.WritableStream | undefined;
#closed: boolean = false;
#writtenSinceFlush: number = 0;
#totalWritten: number = 0;
start(options?: { highWaterMark?: number | undefined; } | undefined): void {
return; // TODO
}
ref(): void {
return; // TODO
}
unref(): void {
return; // TODO
}
write(chunk: string | ArrayBufferView | SharedArrayBuffer | ArrayBuffer): number {
if (this.#closed) {
return typeof chunk === 'string' ? chunk.length : chunk.byteLength;
}
if (this.#stream) {
let data;
if (chunk instanceof ArrayBuffer || chunk instanceof SharedArrayBuffer) data = new Uint8Array(chunk);
else if (!(chunk instanceof Uint8Array) && typeof chunk !== 'string') data = new Uint8Array(chunk.buffer);
else data = chunk;
this.#stream.write(data);
const written = typeof data === 'string' ? data.length : data.byteLength;
this.#totalWritten += written;
return written;
}
if (typeof chunk === 'string') {
fs.appendFileSync(this.#fd, chunk, 'utf8');
this.#writtenSinceFlush += chunk.length;
return chunk.length;
}
if (chunk instanceof ArrayBuffer || chunk instanceof SharedArrayBuffer) fs.appendFileSync(this.#fd, new Uint8Array(chunk));
else fs.appendFileSync(this.#fd, new Uint8Array(chunk.buffer));
this.#writtenSinceFlush += chunk.byteLength;
return chunk.byteLength;
}
//! flushing after writing to a closed FileSink segfaults in Bun but I don't see the need to implement that behavior
flush(): number | Promise<number> {
if (this.#closed) return 0;
// no-op because this is a synchronous implementation
const written = this.#writtenSinceFlush;
this.#writtenSinceFlush = 0;
return written;
}
//! not sure what to do with this error
end(error?: Error): number | Promise<number> {
if (this.#closed) return this.#totalWritten;
const flushed = this.flush();
if (this.#stream) {
this.#stream.end();
this.#closed = true;
return flushed;
}
this.#totalWritten = fs.fstatSync(this.#fd).size;
fs.closeSync(this.#fd);
this.#closed = true;
return flushed;
}
}
import fs from 'node:fs';
import { SystemError } from '../../utils/errors.js';
import type { FileSink as BunFileSink } from 'bun';
export class FileSink implements BunFileSink {
constructor(fdOrPathOrStream: number | string | NodeJS.WritableStream) {
if (typeof fdOrPathOrStream === 'string') try {
this.#fd = fs.openSync(fdOrPathOrStream, 'a+');
fs.ftruncateSync(this.#fd, 0);
} catch (err) {
throw err as SystemError;
}
else if (typeof fdOrPathOrStream === 'number') {
this.#fd = fdOrPathOrStream; // hope this fd is writable
fs.ftruncateSync(this.#fd, 0);
}
else {
this.#stream = fdOrPathOrStream;
}
}
#fd: number = NaN;
#stream: NodeJS.WritableStream | undefined;
#closed: boolean = false;
#writtenSinceFlush: number = 0;
#totalWritten: number = 0;
start(options?: { highWaterMark?: number | undefined; } | undefined): void {
return; // TODO
}
ref(): void {
return; // TODO
}
unref(): void {
return; // TODO
}
write(chunk: string | ArrayBufferView | SharedArrayBuffer | ArrayBuffer): number {
if (this.#closed) {
return typeof chunk === 'string' ? chunk.length : chunk.byteLength;
}
if (this.#stream) {
let data;
if (chunk instanceof ArrayBuffer || chunk instanceof SharedArrayBuffer) data = new Uint8Array(chunk);
else if (!(chunk instanceof Uint8Array) && typeof chunk !== 'string') data = new Uint8Array(chunk.buffer);
else data = chunk;
this.#stream.write(data);
const written = typeof data === 'string' ? data.length : data.byteLength;
this.#totalWritten += written;
return written;
}
if (typeof chunk === 'string') {
fs.appendFileSync(this.#fd, chunk, 'utf8');
this.#writtenSinceFlush += chunk.length;
return chunk.length;
}
if (chunk instanceof ArrayBuffer || chunk instanceof SharedArrayBuffer) fs.appendFileSync(this.#fd, new Uint8Array(chunk));
else fs.appendFileSync(this.#fd, new Uint8Array(chunk.buffer));
this.#writtenSinceFlush += chunk.byteLength;
return chunk.byteLength;
}
//! flushing after writing to a closed FileSink segfaults in Bun but I don't see the need to implement that behavior
flush(): number | Promise<number> {
if (this.#closed) return 0;
// no-op because this is a synchronous implementation
const written = this.#writtenSinceFlush;
this.#writtenSinceFlush = 0;
return written;
}
//! not sure what to do with this error
end(error?: Error): number | Promise<number> {
if (this.#closed) return this.#totalWritten;
const flushed = this.flush();
if (this.#stream) {
this.#stream.end();
this.#closed = true;
return flushed;
}
this.#totalWritten = fs.fstatSync(this.#fd).size;
fs.closeSync(this.#fd);
this.#closed = true;
return flushed;
}
}

View File

@@ -1,185 +1,185 @@
import type { CryptoHashInterface, DigestEncoding, Hash } from 'bun';
import nodecrypto from 'node:crypto';
import os from 'node:os';
import md4, { Md4 } from 'js-md4';
import { wyhash, adler32, crc32, cityhash32, cityhash64, murmur32v3, murmur64v2, murmur32v2 } from '../../../lib/zighash/index.mjs';
export const bunHash = ((data, seed = 0): bigint => wyhash(data, BigInt(seed))) as typeof Bun.hash;
export const bunHashProto: Hash = {
wyhash(data, seed = 0n) { return wyhash(data, seed); },
adler32(data) { return adler32(data); },
crc32(data) { return crc32(data); },
cityHash32(data) { return cityhash32(data); },
cityHash64(data, seed = 0n) { return cityhash64(data, seed); },
murmur32v3(data, seed = 0) { return murmur32v3(data, seed); },
murmur32v2(data, seed = 0) { return murmur32v2(data, seed); },
murmur64v2(data, seed = 0n) { return murmur64v2(data, seed); },
};
type HashImpl = {
digest(): Buffer;
digest(encoding: nodecrypto.BinaryToTextEncoding): string;
update(data: nodecrypto.BinaryLike): HashImpl;
update(data: string, inputEncoding: nodecrypto.Encoding): HashImpl;
};
abstract class BaseHash<T> implements CryptoHashInterface<T> {
readonly #hash: HashImpl | null;
constructor(algorithm: string | HashImpl) {
if (typeof algorithm === 'string') this.#hash = nodecrypto.createHash(algorithm);
// If no preset algorithm is given, expect the subclass to fully implement its own.
else this.#hash = algorithm;
}
update(data: StringOrBuffer) {
if (data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) this.#hash!.update(new Uint8Array(data));
else this.#hash!.update(data);
return this as unknown as T; // is there any good way to do this without asserting?
}
digest(encoding: DigestEncoding): string;
digest(hashInto?: TypedArray): TypedArray;
digest(encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
if (typeof encodingOrHashInto === 'string') {
const encoded = this.#hash!.digest(encodingOrHashInto);
// you'd think node would throw an error if the encoding is invalid, but nope!
// instead it silently returns as if you passed no encoding and gives a Buffer...
if (Buffer.isBuffer(encoded)) throw new TypeError(`Unknown encoding: "${encodingOrHashInto}"`);
else return encoded;
}
const digested = this.#hash!.digest();
if (encodingOrHashInto === undefined) return new Uint8Array(digested.buffer, digested.byteOffset, digested.byteLength);
if (encodingOrHashInto.byteLength < this.byteLength) throw new TypeError(`TypedArray must be at least ${this.byteLength} bytes`);
if (encodingOrHashInto instanceof BigInt64Array || encodingOrHashInto instanceof BigUint64Array) {
// avoid checking endianness for every loop iteration
const endianAwareInsert = os.endianness() === 'LE'
? (arr: string[], j: number, num: string) => arr[7 - j] = num
: (arr: string[], j: number, num: string) => arr[j] = num;
for (let i = 0; i < digested.byteLength; i += 8) {
const bigintStrArr = ['', '', '', '', '', '', '', ''];
for (let j = 0; j < 8; j++) {
const byte = digested[i + j];
if (byte === undefined) break;
endianAwareInsert(bigintStrArr, j, byte.toString(16).padStart(2, '0'));
}
encodingOrHashInto[i / 8] = BigInt(`0x${bigintStrArr.join('')}`);
}
} else {
const HashIntoTypedArray = encodingOrHashInto.constructor as TypedArrayConstructor;
// this will work as long as all hash classes have a byteLength that is a multiple of 4 bytes
encodingOrHashInto.set(new HashIntoTypedArray(digested.buffer, digested.byteOffset, digested.byteLength / HashIntoTypedArray.BYTES_PER_ELEMENT));
}
return encodingOrHashInto;
}
static hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray { return '' };
static readonly byteLength: number;
abstract readonly byteLength: number;
}
export class SHA1 extends BaseHash<SHA1> {
constructor() { super('sha1'); }
static override readonly byteLength = 20;
override readonly byteLength = 20;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class MD4 extends BaseHash<MD4> {
constructor() { //! Not supported by nodecrypto
const hash = md4.create() as unknown as Omit<Md4, 'toString'> & { _update: Md4['update'] };
function digest(): Buffer;
function digest(encoding: nodecrypto.BinaryToTextEncoding): string;
function digest(encoding?: nodecrypto.BinaryToTextEncoding) {
const buf = Buffer.from(hash.arrayBuffer());
if (encoding) return buf.toString(encoding);
else return buf;
}
function update(data: nodecrypto.BinaryLike) {
if (typeof data === 'string') hash._update(data);
else if (data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) hash._update(new Uint8Array(data));
else hash._update(new Uint8Array(data.buffer));
return hash as unknown as MD4HashImpl;
}
type MD4HashImpl = Omit<Md4, 'toString'> & { digest: typeof digest, update: typeof update };
// @ts-expect-error patches to reuse the BaseHash methods
hash.digest = digest; hash._update = hash.update; hash.update = update;
super(hash as unknown as MD4HashImpl);
}
static override readonly byteLength = 16;
override readonly byteLength = 16;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class MD5 extends BaseHash<MD5> {
constructor() { super('md5'); }
static override readonly byteLength = 16;
override readonly byteLength = 16;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA224 extends BaseHash<SHA224> {
constructor() { super('sha224'); }
static override readonly byteLength = 28;
override readonly byteLength = 28;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA512 extends BaseHash<SHA512> {
constructor() { super('sha512'); }
static override readonly byteLength = 64;
override readonly byteLength = 64;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA384 extends BaseHash<SHA384> {
constructor() { super('sha384'); }
static override readonly byteLength = 48;
override readonly byteLength = 48;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA256 extends BaseHash<SHA256> {
constructor() { super('sha256'); }
static override readonly byteLength = 32;
override readonly byteLength = 32;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA512_256 extends BaseHash<SHA512_256> {
constructor() { super('sha512-256'); }
static override readonly byteLength = 32;
override readonly byteLength = 32;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
import type { CryptoHashInterface, DigestEncoding, Hash } from 'bun';
import nodecrypto from 'node:crypto';
import os from 'node:os';
import md4, { Md4 } from 'js-md4';
import { wyhash, adler32, crc32, cityhash32, cityhash64, murmur32v3, murmur64v2, murmur32v2 } from '../../../lib/zighash/index.mjs';
export const bunHash = ((data, seed = 0): bigint => wyhash(data, BigInt(seed))) as typeof Bun.hash;
export const bunHashProto: Hash = {
wyhash(data, seed = 0n) { return wyhash(data, seed); },
adler32(data) { return adler32(data); },
crc32(data) { return crc32(data); },
cityHash32(data) { return cityhash32(data); },
cityHash64(data, seed = 0n) { return cityhash64(data, seed); },
murmur32v3(data, seed = 0) { return murmur32v3(data, seed); },
murmur32v2(data, seed = 0) { return murmur32v2(data, seed); },
murmur64v2(data, seed = 0n) { return murmur64v2(data, seed); },
};
type HashImpl = {
digest(): Buffer;
digest(encoding: nodecrypto.BinaryToTextEncoding): string;
update(data: nodecrypto.BinaryLike): HashImpl;
update(data: string, inputEncoding: nodecrypto.Encoding): HashImpl;
};
abstract class BaseHash<T> implements CryptoHashInterface<T> {
readonly #hash: HashImpl | null;
constructor(algorithm: string | HashImpl) {
if (typeof algorithm === 'string') this.#hash = nodecrypto.createHash(algorithm);
// If no preset algorithm is given, expect the subclass to fully implement its own.
else this.#hash = algorithm;
}
update(data: StringOrBuffer) {
if (data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) this.#hash!.update(new Uint8Array(data));
else this.#hash!.update(data);
return this as unknown as T; // is there any good way to do this without asserting?
}
digest(encoding: DigestEncoding): string;
digest(hashInto?: TypedArray): TypedArray;
digest(encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
if (typeof encodingOrHashInto === 'string') {
const encoded = this.#hash!.digest(encodingOrHashInto);
// you'd think node would throw an error if the encoding is invalid, but nope!
// instead it silently returns as if you passed no encoding and gives a Buffer...
if (Buffer.isBuffer(encoded)) throw new TypeError(`Unknown encoding: "${encodingOrHashInto}"`);
else return encoded;
}
const digested = this.#hash!.digest();
if (encodingOrHashInto === undefined) return new Uint8Array(digested.buffer, digested.byteOffset, digested.byteLength);
if (encodingOrHashInto.byteLength < this.byteLength) throw new TypeError(`TypedArray must be at least ${this.byteLength} bytes`);
if (encodingOrHashInto instanceof BigInt64Array || encodingOrHashInto instanceof BigUint64Array) {
// avoid checking endianness for every loop iteration
const endianAwareInsert = os.endianness() === 'LE'
? (arr: string[], j: number, num: string) => arr[7 - j] = num
: (arr: string[], j: number, num: string) => arr[j] = num;
for (let i = 0; i < digested.byteLength; i += 8) {
const bigintStrArr = ['', '', '', '', '', '', '', ''];
for (let j = 0; j < 8; j++) {
const byte = digested[i + j];
if (byte === undefined) break;
endianAwareInsert(bigintStrArr, j, byte.toString(16).padStart(2, '0'));
}
encodingOrHashInto[i / 8] = BigInt(`0x${bigintStrArr.join('')}`);
}
} else {
const HashIntoTypedArray = encodingOrHashInto.constructor as TypedArrayConstructor;
// this will work as long as all hash classes have a byteLength that is a multiple of 4 bytes
encodingOrHashInto.set(new HashIntoTypedArray(digested.buffer, digested.byteOffset, digested.byteLength / HashIntoTypedArray.BYTES_PER_ELEMENT));
}
return encodingOrHashInto;
}
static hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray { return '' };
static readonly byteLength: number;
abstract readonly byteLength: number;
}
export class SHA1 extends BaseHash<SHA1> {
constructor() { super('sha1'); }
static override readonly byteLength = 20;
override readonly byteLength = 20;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class MD4 extends BaseHash<MD4> {
constructor() { //! Not supported by nodecrypto
const hash = md4.create() as unknown as Omit<Md4, 'toString'> & { _update: Md4['update'] };
function digest(): Buffer;
function digest(encoding: nodecrypto.BinaryToTextEncoding): string;
function digest(encoding?: nodecrypto.BinaryToTextEncoding) {
const buf = Buffer.from(hash.arrayBuffer());
if (encoding) return buf.toString(encoding);
else return buf;
}
function update(data: nodecrypto.BinaryLike) {
if (typeof data === 'string') hash._update(data);
else if (data instanceof ArrayBuffer || data instanceof SharedArrayBuffer) hash._update(new Uint8Array(data));
else hash._update(new Uint8Array(data.buffer));
return hash as unknown as MD4HashImpl;
}
type MD4HashImpl = Omit<Md4, 'toString'> & { digest: typeof digest, update: typeof update };
// @ts-expect-error patches to reuse the BaseHash methods
hash.digest = digest; hash._update = hash.update; hash.update = update;
super(hash as unknown as MD4HashImpl);
}
static override readonly byteLength = 16;
override readonly byteLength = 16;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class MD5 extends BaseHash<MD5> {
constructor() { super('md5'); }
static override readonly byteLength = 16;
override readonly byteLength = 16;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA224 extends BaseHash<SHA224> {
constructor() { super('sha224'); }
static override readonly byteLength = 28;
override readonly byteLength = 28;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA512 extends BaseHash<SHA512> {
constructor() { super('sha512'); }
static override readonly byteLength = 64;
override readonly byteLength = 64;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA384 extends BaseHash<SHA384> {
constructor() { super('sha384'); }
static override readonly byteLength = 48;
override readonly byteLength = 48;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA256 extends BaseHash<SHA256> {
constructor() { super('sha256'); }
static override readonly byteLength = 32;
override readonly byteLength = 32;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}
export class SHA512_256 extends BaseHash<SHA512_256> {
constructor() { super('sha512-256'); }
static override readonly byteLength = 32;
override readonly byteLength = 32;
static override hash(data: StringOrBuffer, encoding?: DigestEncoding): string;
static override hash(data: StringOrBuffer, hashInto?: TypedArray): TypedArray;
static override hash(data: StringOrBuffer, encodingOrHashInto?: DigestEncoding | TypedArray): string | TypedArray {
const instance = new this(); instance.update(data);
return instance.digest(encodingOrHashInto as DigestEncoding & TypedArray);
}
}

View File

@@ -1,96 +1,96 @@
import type { JavaScriptLoader, TranspilerOptions, Transpiler as BunTranspiler, Import } from 'bun';
import { transformSync, scan, init } from 'bun-wasm';
import { Message } from 'bun-wasm/schema';
import $ from 'chalk';
await init();
enum InternalImportKind {
'entry-point' = 1, // entry_point
'import-statement' = 2, // stmt
'require-call' = 3, // require
'dynamic-import' = 4, // dynamic
'require-resolve' = 5, // require_resolve
'import-rule' = 6, // at
'url-token' = 7, // url
'internal' = 8, // internal
}
export type ScanImportsEntry = {
kind: 'import-statement' | 'dynamic-import';
path: string;
};
export default class Transpiler implements BunTranspiler {
constructor(options?: TranspilerOptions) {
this.#options = options ?? {};
this.#rootFile = 'input.tsx'; // + (this.#options.loader ?? 'tsx');
//? ^ NOTE: with current bun-wasm builds, the loader option is ignored and hardcoded to tsx
}
#options: TranspilerOptions;
#rootFile: string;
#decoder?: TextDecoder;
#internallyCalled: boolean = false;
async transform(code: StringOrBuffer, loader: JavaScriptLoader): Promise<string> {
this.#internallyCalled = true;
return this.transformSync(code, loader);
}
transformSync(code: StringOrBuffer, ctx: object): string;
transformSync(code: StringOrBuffer, loader: JavaScriptLoader, ctx: object): string;
transformSync(code: StringOrBuffer, loader?: JavaScriptLoader | undefined): string;
transformSync(code: StringOrBuffer, loader?: JavaScriptLoader | object, ctx: object = {}): string {
if (!code) return ''; // wasm dies with empty string input
if (typeof code !== 'string' && !(code instanceof Uint8Array)) throw new TypeError('code must be a string or Uint8Array');
if (typeof loader !== 'string') loader = this.#options.loader;
const result = transformSync(code, this.#rootFile, loader);
// status 1 = success, status 2 = error
if (result.status === 2) throw formatBuildErrors(result.errors, this.#internallyCalled ? this.transform : this.transformSync);
this.#internallyCalled = false;
this.#decoder ??= new TextDecoder();
return this.#decoder.decode(result.files[0].data);
}
scan(code: StringOrBuffer): { exports: string[]; imports: Import[]; } {
if (!code) return { exports: [], imports: [] }; // wasm dies with empty string input
if (typeof code !== 'string' && !(code instanceof Uint8Array)) throw new TypeError('code must be a string or Uint8Array');
const result = scan(code, this.#rootFile, this.#options.loader);
if (result.errors.length) throw formatBuildErrors(result.errors, this.#internallyCalled ? this.scanImports : this.scan);
this.#internallyCalled = false;
result.imports.forEach(imp => (imp.kind as unknown) = InternalImportKind[imp.kind]);
return {
exports: result.exports,
imports: result.imports as unknown as Import[],
};
}
scanImports(code: StringOrBuffer): ScanImportsEntry[] {
this.#internallyCalled = true;
return this.scan(code).imports.filter(imp => imp.kind === 'import-statement' || imp.kind === 'dynamic-import') as ScanImportsEntry[];
}
}
function formatBuildErrors(buildErrors: Message[], caller: Transpiler[keyof Transpiler]): AggregateError {
const formatted = buildErrors.map(err => {
const loc = err.data.location;
const str = `${$.redBright('error')}${$.gray(':')} ${$.bold(err.data.text)}\n` +
(loc
? `${highlightErrorChar(loc.line_text, loc.offset)}\n` +
$.redBright.bold('^'.padStart(loc.column)) + '\n' +
`${$.bold(loc.file)}${$.gray(':')}${$.yellowBright(loc.line)}${$.gray(':')}${$.yellowBright(loc.column)} ${$.gray(loc.offset)}`
: ''
);
return { __proto__: Error.prototype, stack: str };
});
const aggregate = new AggregateError(formatted, `Input code has ${formatted.length} error${formatted.length === 1 ? '' : 's'}`);
Error.captureStackTrace(aggregate, caller);
aggregate.name = 'BuildError';
return aggregate;
}
function highlightErrorChar(str: string, at: number): string {
return str.slice(0, at) + $.red(str[at]) + str.slice(at + 1);
}
import type { JavaScriptLoader, TranspilerOptions, Transpiler as BunTranspiler, Import } from 'bun';
import { transformSync, scan, init } from 'bun-wasm';
import { Message } from 'bun-wasm/schema';
import $ from 'chalk';
await init();
enum InternalImportKind {
'entry-point' = 1, // entry_point
'import-statement' = 2, // stmt
'require-call' = 3, // require
'dynamic-import' = 4, // dynamic
'require-resolve' = 5, // require_resolve
'import-rule' = 6, // at
'url-token' = 7, // url
'internal' = 8, // internal
}
export type ScanImportsEntry = {
kind: 'import-statement' | 'dynamic-import';
path: string;
};
export default class Transpiler implements BunTranspiler {
constructor(options?: TranspilerOptions) {
this.#options = options ?? {};
this.#rootFile = 'input.tsx'; // + (this.#options.loader ?? 'tsx');
//? ^ NOTE: with current bun-wasm builds, the loader option is ignored and hardcoded to tsx
}
#options: TranspilerOptions;
#rootFile: string;
#decoder?: TextDecoder;
#internallyCalled: boolean = false;
async transform(code: StringOrBuffer, loader: JavaScriptLoader): Promise<string> {
this.#internallyCalled = true;
return this.transformSync(code, loader);
}
transformSync(code: StringOrBuffer, ctx: object): string;
transformSync(code: StringOrBuffer, loader: JavaScriptLoader, ctx: object): string;
transformSync(code: StringOrBuffer, loader?: JavaScriptLoader | undefined): string;
transformSync(code: StringOrBuffer, loader?: JavaScriptLoader | object, ctx: object = {}): string {
if (!code) return ''; // wasm dies with empty string input
if (typeof code !== 'string' && !(code instanceof Uint8Array)) throw new TypeError('code must be a string or Uint8Array');
if (typeof loader !== 'string') loader = this.#options.loader;
const result = transformSync(code, this.#rootFile, loader);
// status 1 = success, status 2 = error
if (result.status === 2) throw formatBuildErrors(result.errors, this.#internallyCalled ? this.transform : this.transformSync);
this.#internallyCalled = false;
this.#decoder ??= new TextDecoder();
return this.#decoder.decode(result.files[0].data);
}
scan(code: StringOrBuffer): { exports: string[]; imports: Import[]; } {
if (!code) return { exports: [], imports: [] }; // wasm dies with empty string input
if (typeof code !== 'string' && !(code instanceof Uint8Array)) throw new TypeError('code must be a string or Uint8Array');
const result = scan(code, this.#rootFile, this.#options.loader);
if (result.errors.length) throw formatBuildErrors(result.errors, this.#internallyCalled ? this.scanImports : this.scan);
this.#internallyCalled = false;
result.imports.forEach(imp => (imp.kind as unknown) = InternalImportKind[imp.kind]);
return {
exports: result.exports,
imports: result.imports as unknown as Import[],
};
}
scanImports(code: StringOrBuffer): ScanImportsEntry[] {
this.#internallyCalled = true;
return this.scan(code).imports.filter(imp => imp.kind === 'import-statement' || imp.kind === 'dynamic-import') as ScanImportsEntry[];
}
}
function formatBuildErrors(buildErrors: Message[], caller: Transpiler[keyof Transpiler]): AggregateError {
const formatted = buildErrors.map(err => {
const loc = err.data.location;
const str = `${$.redBright('error')}${$.gray(':')} ${$.bold(err.data.text)}\n` +
(loc
? `${highlightErrorChar(loc.line_text, loc.offset)}\n` +
$.redBright.bold('^'.padStart(loc.column)) + '\n' +
`${$.bold(loc.file)}${$.gray(':')}${$.yellowBright(loc.line)}${$.gray(':')}${$.yellowBright(loc.column)} ${$.gray(loc.offset)}`
: ''
);
return { __proto__: Error.prototype, stack: str };
});
const aggregate = new AggregateError(formatted, `Input code has ${formatted.length} error${formatted.length === 1 ? '' : 's'}`);
Error.captureStackTrace(aggregate, caller);
aggregate.name = 'BuildError';
return aggregate;
}
function highlightErrorChar(str: string, at: number): string {
return str.slice(0, at) + $.red(str[at]) + str.slice(at + 1);
}

View File

@@ -1,72 +1,72 @@
declare module 'js-md4' {
export type MD4Input = string | ArrayBuffer | Uint8Array | number[];
interface md4 {
/**
* # Broken, will throw an error.
* @deprecated Use {@link md4.hex} instead.
*/
(input: MD4Input): never;
/** Creates an `Md4` hasher instance. */
create(): Md4;
/** Shortcut for `md4.create().update(...)` */
update(message: MD4Input): Md4;
/** Hash `message` into a hex string. */
hex(message: MD4Input): string;
/** Hash `message` into an Array. */
array(message: MD4Input): number[];
/** Identical to {@link md4.array}. */
digest(message: MD4Input): number[];
/**
* Identical to {@link md4.arrayBuffer}.
* @deprecated Use {@link md4.arrayBuffer} instead.
*/
buffer(message: MD4Input): ArrayBuffer;
/** Hash `message` into an ArrayBuffer. */
arrayBuffer(message: MD4Input): ArrayBuffer;
}
export type Md4 = Md4;
declare class Md4 {
private constructor();
private toString(): string;
private finalize(): void;
private hash(): void;
/**
* Append `message` to the internal hash source data.
* @returns A reference to `this` for chaining, or nothing if the instance has been finalized.
*/
update(message: MD4Input): this | void;
/** Hash into a hex string. Finalizes the hash. */
hex(): string;
/** Hash into an Array. Finalizes the hash. */
array(): number[];
/** Identical to {@link Md4.array}. */
digest(): number[];
/**
* Identical to {@link Md4.arrayBuffer}.
* @deprecated Use {@link Md4.arrayBuffer} instead.
*/
buffer(): ArrayBuffer;
/** Hash into an ArrayBuffer. Finalizes the hash. */
arrayBuffer(): ArrayBuffer;
private buffer8: Uint8Array;
private blocks: Uint32Array;
private bytes: number;
private start: number;
private h3: number;
private h2: number;
private h1: number;
private h0: number;
readonly hashed: boolean;
/** If true, `update()` operations will silently fail. */
readonly finalized: boolean;
readonly first: boolean;
private lastByteIndex?: number;
}
const md4: md4;
export default md4;
}
declare module 'js-md4' {
export type MD4Input = string | ArrayBuffer | Uint8Array | number[];
interface md4 {
/**
* # Broken, will throw an error.
* @deprecated Use {@link md4.hex} instead.
*/
(input: MD4Input): never;
/** Creates an `Md4` hasher instance. */
create(): Md4;
/** Shortcut for `md4.create().update(...)` */
update(message: MD4Input): Md4;
/** Hash `message` into a hex string. */
hex(message: MD4Input): string;
/** Hash `message` into an Array. */
array(message: MD4Input): number[];
/** Identical to {@link md4.array}. */
digest(message: MD4Input): number[];
/**
* Identical to {@link md4.arrayBuffer}.
* @deprecated Use {@link md4.arrayBuffer} instead.
*/
buffer(message: MD4Input): ArrayBuffer;
/** Hash `message` into an ArrayBuffer. */
arrayBuffer(message: MD4Input): ArrayBuffer;
}
export type Md4 = Md4;
declare class Md4 {
private constructor();
private toString(): string;
private finalize(): void;
private hash(): void;
/**
* Append `message` to the internal hash source data.
* @returns A reference to `this` for chaining, or nothing if the instance has been finalized.
*/
update(message: MD4Input): this | void;
/** Hash into a hex string. Finalizes the hash. */
hex(): string;
/** Hash into an Array. Finalizes the hash. */
array(): number[];
/** Identical to {@link Md4.array}. */
digest(): number[];
/**
* Identical to {@link Md4.arrayBuffer}.
* @deprecated Use {@link Md4.arrayBuffer} instead.
*/
buffer(): ArrayBuffer;
/** Hash into an ArrayBuffer. Finalizes the hash. */
arrayBuffer(): ArrayBuffer;
private buffer8: Uint8Array;
private blocks: Uint32Array;
private bytes: number;
private start: number;
private h3: number;
private h2: number;
private h1: number;
private h0: number;
readonly hashed: boolean;
/** If true, `update()` operations will silently fail. */
readonly finalized: boolean;
readonly first: boolean;
private lastByteIndex?: number;
}
const md4: md4;
export default md4;
}

View File

@@ -1,230 +1,230 @@
type PosixErrNo = MapKeysType<ReturnType<typeof getPosixSystemErrorMap>>;
type Win32ErrNo = MapKeysType<ReturnType<typeof getWin32SystemErrorMap>>;
export function getCallSites(sliceOff = 1) {
const originalPST = Error.prepareStackTrace;
Error.prepareStackTrace = (error, stack) => stack;
const { stack } = new Error();
if (stack?.constructor.name !== 'Array') throw new Error('Failed to acquire structured JS stack trace');
Error.prepareStackTrace = originalPST;
return (stack as unknown as NodeJS.CallSite[]).slice(sliceOff);
}
export function getPosixSystemErrorMap() {
return new Map([
[ -7, [ 'E2BIG', 'argument list too long' ] ],
[ -13, [ 'EACCES', 'permission denied' ] ],
[ -98, [ 'EADDRINUSE', 'address already in use' ] ],
[ -99, [ 'EADDRNOTAVAIL', 'address not available' ] ],
[ -97, [ 'EAFNOSUPPORT', 'address family not supported' ] ],
[ -11, [ 'EAGAIN', 'resource temporarily unavailable' ] ],
[ -3000, [ 'EAI_ADDRFAMILY', 'address family not supported' ] ],
[ -3001, [ 'EAI_AGAIN', 'temporary failure' ] ],
[ -3002, [ 'EAI_BADFLAGS', 'bad ai_flags value' ] ],
[ -3013, [ 'EAI_BADHINTS', 'invalid value for hints' ] ],
[ -3003, [ 'EAI_CANCELED', 'request canceled' ] ],
[ -3004, [ 'EAI_FAIL', 'permanent failure' ] ],
[ -3005, [ 'EAI_FAMILY', 'ai_family not supported' ] ],
[ -3006, [ 'EAI_MEMORY', 'out of memory' ] ],
[ -3007, [ 'EAI_NODATA', 'no address' ] ],
[ -3008, [ 'EAI_NONAME', 'unknown node or service' ] ],
[ -3009, [ 'EAI_OVERFLOW', 'argument buffer overflow' ] ],
[ -3014, [ 'EAI_PROTOCOL', 'resolved protocol is unknown' ] ],
[ -3010, [ 'EAI_SERVICE', 'service not available for socket type' ] ],
[ -3011, [ 'EAI_SOCKTYPE', 'socket type not supported' ] ],
[ -114, [ 'EALREADY', 'connection already in progress' ] ],
[ -9, [ 'EBADF', 'bad file descriptor' ] ],
[ -16, [ 'EBUSY', 'resource busy or locked' ] ],
[ -125, [ 'ECANCELED', 'operation canceled' ] ],
[ -4080, [ 'ECHARSET', 'invalid Unicode character' ] ],
[ -103, [ 'ECONNABORTED', 'software caused connection abort' ] ],
[ -111, [ 'ECONNREFUSED', 'connection refused' ] ],
[ -104, [ 'ECONNRESET', 'connection reset by peer' ] ],
[ -89, [ 'EDESTADDRREQ', 'destination address required' ] ],
[ -17, [ 'EEXIST', 'file already exists' ] ],
[ -14, [ 'EFAULT', 'bad address in system call argument' ] ],
[ -27, [ 'EFBIG', 'file too large' ] ],
[ -113, [ 'EHOSTUNREACH', 'host is unreachable' ] ],
[ -4, [ 'EINTR', 'interrupted system call' ] ],
[ -22, [ 'EINVAL', 'invalid argument' ] ],
[ -5, [ 'EIO', 'i/o error' ] ],
[ -106, [ 'EISCONN', 'socket is already connected' ] ],
[ -21, [ 'EISDIR', 'illegal operation on a directory' ] ],
[ -40, [ 'ELOOP', 'too many symbolic links encountered' ] ],
[ -24, [ 'EMFILE', 'too many open files' ] ],
[ -90, [ 'EMSGSIZE', 'message too long' ] ],
[ -36, [ 'ENAMETOOLONG', 'name too long' ] ],
[ -100, [ 'ENETDOWN', 'network is down' ] ],
[ -101, [ 'ENETUNREACH', 'network is unreachable' ] ],
[ -23, [ 'ENFILE', 'file table overflow' ] ],
[ -105, [ 'ENOBUFS', 'no buffer space available' ] ],
[ -19, [ 'ENODEV', 'no such device' ] ],
[ -2, [ 'ENOENT', 'no such file or directory' ] ],
[ -12, [ 'ENOMEM', 'not enough memory' ] ],
[ -64, [ 'ENONET', 'machine is not on the network' ] ],
[ -92, [ 'ENOPROTOOPT', 'protocol not available' ] ],
[ -28, [ 'ENOSPC', 'no space left on device' ] ],
[ -38, [ 'ENOSYS', 'function not implemented' ] ],
[ -107, [ 'ENOTCONN', 'socket is not connected' ] ],
[ -20, [ 'ENOTDIR', 'not a directory' ] ],
[ -39, [ 'ENOTEMPTY', 'directory not empty' ] ],
[ -88, [ 'ENOTSOCK', 'socket operation on non-socket' ] ],
[ -95, [ 'ENOTSUP', 'operation not supported on socket' ] ],
[ -75, [ 'EOVERFLOW', 'value too large for defined data type' ] ],
[ -1, [ 'EPERM', 'operation not permitted' ] ],
[ -32, [ 'EPIPE', 'broken pipe' ] ],
[ -71, [ 'EPROTO', 'protocol error' ] ],
[ -93, [ 'EPROTONOSUPPORT', 'protocol not supported' ] ],
[ -91, [ 'EPROTOTYPE', 'protocol wrong type for socket' ] ],
[ -34, [ 'ERANGE', 'result too large' ] ],
[ -30, [ 'EROFS', 'read-only file system' ] ],
[ -108, [ 'ESHUTDOWN', 'cannot send after transport endpoint shutdown' ] ],
[ -29, [ 'ESPIPE', 'invalid seek' ] ],
[ -3, [ 'ESRCH', 'no such process' ] ],
[ -110, [ 'ETIMEDOUT', 'connection timed out' ] ],
[ -26, [ 'ETXTBSY', 'text file is busy' ] ],
[ -18, [ 'EXDEV', 'cross-device link not permitted' ] ],
[ -4094, [ 'UNKNOWN', 'unknown error' ] ],
[ -4095, [ 'EOF', 'end of file' ] ],
[ -6, [ 'ENXIO', 'no such device or address' ] ],
[ -31, [ 'EMLINK', 'too many links' ] ],
[ -112, [ 'EHOSTDOWN', 'host is down' ] ],
[ -121, [ 'EREMOTEIO', 'remote I/O error' ] ],
[ -25, [ 'ENOTTY', 'inappropriate ioctl for device' ] ],
[ -4028, [ 'EFTYPE', 'inappropriate file type or format' ] ],
[ -84, [ 'EILSEQ', 'illegal byte sequence' ] ],
[ -94, [ 'ESOCKTNOSUPPORT', 'socket type not supported' ] ]
] as const);
}
export function getWin32SystemErrorMap() {
return new Map([
[ -4093, [ 'E2BIG', 'argument list too long' ] ],
[ -4092, [ 'EACCES', 'permission denied' ] ],
[ -4091, [ 'EADDRINUSE', 'address already in use' ] ],
[ -4090, [ 'EADDRNOTAVAIL', 'address not available' ] ],
[ -4089, [ 'EAFNOSUPPORT', 'address family not supported' ] ],
[ -4088, [ 'EAGAIN', 'resource temporarily unavailable' ] ],
[ -3000, [ 'EAI_ADDRFAMILY', 'address family not supported' ] ],
[ -3001, [ 'EAI_AGAIN', 'temporary failure' ] ],
[ -3002, [ 'EAI_BADFLAGS', 'bad ai_flags value' ] ],
[ -3013, [ 'EAI_BADHINTS', 'invalid value for hints' ] ],
[ -3003, [ 'EAI_CANCELED', 'request canceled' ] ],
[ -3004, [ 'EAI_FAIL', 'permanent failure' ] ],
[ -3005, [ 'EAI_FAMILY', 'ai_family not supported' ] ],
[ -3006, [ 'EAI_MEMORY', 'out of memory' ] ],
[ -3007, [ 'EAI_NODATA', 'no address' ] ],
[ -3008, [ 'EAI_NONAME', 'unknown node or service' ] ],
[ -3009, [ 'EAI_OVERFLOW', 'argument buffer overflow' ] ],
[ -3014, [ 'EAI_PROTOCOL', 'resolved protocol is unknown' ] ],
[ -3010, [ 'EAI_SERVICE', 'service not available for socket type' ] ],
[ -3011, [ 'EAI_SOCKTYPE', 'socket type not supported' ] ],
[ -4084, [ 'EALREADY', 'connection already in progress' ] ],
[ -4083, [ 'EBADF', 'bad file descriptor' ] ],
[ -4082, [ 'EBUSY', 'resource busy or locked' ] ],
[ -4081, [ 'ECANCELED', 'operation canceled' ] ],
[ -4080, [ 'ECHARSET', 'invalid Unicode character' ] ],
[ -4079, [ 'ECONNABORTED', 'software caused connection abort' ] ],
[ -4078, [ 'ECONNREFUSED', 'connection refused' ] ],
[ -4077, [ 'ECONNRESET', 'connection reset by peer' ] ],
[ -4076, [ 'EDESTADDRREQ', 'destination address required' ] ],
[ -4075, [ 'EEXIST', 'file already exists' ] ],
[ -4074, [ 'EFAULT', 'bad address in system call argument' ] ],
[ -4036, [ 'EFBIG', 'file too large' ] ],
[ -4073, [ 'EHOSTUNREACH', 'host is unreachable' ] ],
[ -4072, [ 'EINTR', 'interrupted system call' ] ],
[ -4071, [ 'EINVAL', 'invalid argument' ] ],
[ -4070, [ 'EIO', 'i/o error' ] ],
[ -4069, [ 'EISCONN', 'socket is already connected' ] ],
[ -4068, [ 'EISDIR', 'illegal operation on a directory' ] ],
[ -4067, [ 'ELOOP', 'too many symbolic links encountered' ] ],
[ -4066, [ 'EMFILE', 'too many open files' ] ],
[ -4065, [ 'EMSGSIZE', 'message too long' ] ],
[ -4064, [ 'ENAMETOOLONG', 'name too long' ] ],
[ -4063, [ 'ENETDOWN', 'network is down' ] ],
[ -4062, [ 'ENETUNREACH', 'network is unreachable' ] ],
[ -4061, [ 'ENFILE', 'file table overflow' ] ],
[ -4060, [ 'ENOBUFS', 'no buffer space available' ] ],
[ -4059, [ 'ENODEV', 'no such device' ] ],
[ -4058, [ 'ENOENT', 'no such file or directory' ] ],
[ -4057, [ 'ENOMEM', 'not enough memory' ] ],
[ -4056, [ 'ENONET', 'machine is not on the network' ] ],
[ -4035, [ 'ENOPROTOOPT', 'protocol not available' ] ],
[ -4055, [ 'ENOSPC', 'no space left on device' ] ],
[ -4054, [ 'ENOSYS', 'function not implemented' ] ],
[ -4053, [ 'ENOTCONN', 'socket is not connected' ] ],
[ -4052, [ 'ENOTDIR', 'not a directory' ] ],
[ -4051, [ 'ENOTEMPTY', 'directory not empty' ] ],
[ -4050, [ 'ENOTSOCK', 'socket operation on non-socket' ] ],
[ -4049, [ 'ENOTSUP', 'operation not supported on socket' ] ],
[ -4026, [ 'EOVERFLOW', 'value too large for defined data type' ] ],
[ -4048, [ 'EPERM', 'operation not permitted' ] ],
[ -4047, [ 'EPIPE', 'broken pipe' ] ],
[ -4046, [ 'EPROTO', 'protocol error' ] ],
[ -4045, [ 'EPROTONOSUPPORT', 'protocol not supported' ] ],
[ -4044, [ 'EPROTOTYPE', 'protocol wrong type for socket' ] ],
[ -4034, [ 'ERANGE', 'result too large' ] ],
[ -4043, [ 'EROFS', 'read-only file system' ] ],
[ -4042, [ 'ESHUTDOWN', 'cannot send after transport endpoint shutdown' ] ],
[ -4041, [ 'ESPIPE', 'invalid seek' ] ],
[ -4040, [ 'ESRCH', 'no such process' ] ],
[ -4039, [ 'ETIMEDOUT', 'connection timed out' ] ],
[ -4038, [ 'ETXTBSY', 'text file is busy' ] ],
[ -4037, [ 'EXDEV', 'cross-device link not permitted' ] ],
[ -4094, [ 'UNKNOWN', 'unknown error' ] ],
[ -4095, [ 'EOF', 'end of file' ] ],
[ -4033, [ 'ENXIO', 'no such device or address' ] ],
[ -4032, [ 'EMLINK', 'too many links' ] ],
[ -4031, [ 'EHOSTDOWN', 'host is down' ] ],
[ -4030, [ 'EREMOTEIO', 'remote I/O error' ] ],
[ -4029, [ 'ENOTTY', 'inappropriate ioctl for device' ] ],
[ -4028, [ 'EFTYPE', 'inappropriate file type or format' ] ],
[ -4027, [ 'EILSEQ', 'illegal byte sequence' ] ],
[ -4025, [ 'ESOCKTNOSUPPORT', 'socket type not supported' ] ]
] as const);
}
export function getPosixToWin32SystemErrorMap() {
const posixEntries = [...getPosixSystemErrorMap().entries()];
const win32Entries = [...getWin32SystemErrorMap().entries()];
const map: Map<PosixErrNo, Win32ErrNo> = new Map();
posixEntries.forEach(([code, val]) => {
const found = win32Entries.find(([_, v]) => v[0] === val[0]);
if (!found) console.error(val[0]);
else map.set(code, found[0]);
});
return map;
}
export function getPlatformSystemErrorFromPosix(posixErrNo: PosixErrNo) {
if (process.platform === 'win32') {
const win32errno = getPosixToWin32SystemErrorMap().get(posixErrNo)!;
return getWin32SystemErrorMap().get(win32errno);
} else {
return getPosixSystemErrorMap().get(posixErrNo);
}
}
export class SystemError extends Error {
constructor(errno: PosixErrNo, syscall?: string, errpath?: string) {
const [errname, errmsg] = getPlatformSystemErrorFromPosix(errno) ?? ['SystemError', 'Unknown system error'];
super(errmsg);
this.name = errname;
this.code = errname;
this.errno = errno;
if (syscall) this.syscall = syscall;
if (errpath) this.path = errpath;
}
errno?: number | undefined;
code?: string | undefined;
path?: string | undefined;
syscall?: string | undefined;
}
export class NotImplementedError extends Error {
constructor(thing: string, func: AnyCallable = NotImplementedError, overrideMsg: boolean = false) {
super(overrideMsg ? thing : `A polyfill for ${thing} is not yet implemented by bun-polyfills.`);
this.name = 'NotImplementedError';
Error.captureStackTrace(this, func);
}
}
type PosixErrNo = MapKeysType<ReturnType<typeof getPosixSystemErrorMap>>;
type Win32ErrNo = MapKeysType<ReturnType<typeof getWin32SystemErrorMap>>;
export function getCallSites(sliceOff = 1) {
const originalPST = Error.prepareStackTrace;
Error.prepareStackTrace = (error, stack) => stack;
const { stack } = new Error();
if (stack?.constructor.name !== 'Array') throw new Error('Failed to acquire structured JS stack trace');
Error.prepareStackTrace = originalPST;
return (stack as unknown as NodeJS.CallSite[]).slice(sliceOff);
}
export function getPosixSystemErrorMap() {
return new Map([
[ -7, [ 'E2BIG', 'argument list too long' ] ],
[ -13, [ 'EACCES', 'permission denied' ] ],
[ -98, [ 'EADDRINUSE', 'address already in use' ] ],
[ -99, [ 'EADDRNOTAVAIL', 'address not available' ] ],
[ -97, [ 'EAFNOSUPPORT', 'address family not supported' ] ],
[ -11, [ 'EAGAIN', 'resource temporarily unavailable' ] ],
[ -3000, [ 'EAI_ADDRFAMILY', 'address family not supported' ] ],
[ -3001, [ 'EAI_AGAIN', 'temporary failure' ] ],
[ -3002, [ 'EAI_BADFLAGS', 'bad ai_flags value' ] ],
[ -3013, [ 'EAI_BADHINTS', 'invalid value for hints' ] ],
[ -3003, [ 'EAI_CANCELED', 'request canceled' ] ],
[ -3004, [ 'EAI_FAIL', 'permanent failure' ] ],
[ -3005, [ 'EAI_FAMILY', 'ai_family not supported' ] ],
[ -3006, [ 'EAI_MEMORY', 'out of memory' ] ],
[ -3007, [ 'EAI_NODATA', 'no address' ] ],
[ -3008, [ 'EAI_NONAME', 'unknown node or service' ] ],
[ -3009, [ 'EAI_OVERFLOW', 'argument buffer overflow' ] ],
[ -3014, [ 'EAI_PROTOCOL', 'resolved protocol is unknown' ] ],
[ -3010, [ 'EAI_SERVICE', 'service not available for socket type' ] ],
[ -3011, [ 'EAI_SOCKTYPE', 'socket type not supported' ] ],
[ -114, [ 'EALREADY', 'connection already in progress' ] ],
[ -9, [ 'EBADF', 'bad file descriptor' ] ],
[ -16, [ 'EBUSY', 'resource busy or locked' ] ],
[ -125, [ 'ECANCELED', 'operation canceled' ] ],
[ -4080, [ 'ECHARSET', 'invalid Unicode character' ] ],
[ -103, [ 'ECONNABORTED', 'software caused connection abort' ] ],
[ -111, [ 'ECONNREFUSED', 'connection refused' ] ],
[ -104, [ 'ECONNRESET', 'connection reset by peer' ] ],
[ -89, [ 'EDESTADDRREQ', 'destination address required' ] ],
[ -17, [ 'EEXIST', 'file already exists' ] ],
[ -14, [ 'EFAULT', 'bad address in system call argument' ] ],
[ -27, [ 'EFBIG', 'file too large' ] ],
[ -113, [ 'EHOSTUNREACH', 'host is unreachable' ] ],
[ -4, [ 'EINTR', 'interrupted system call' ] ],
[ -22, [ 'EINVAL', 'invalid argument' ] ],
[ -5, [ 'EIO', 'i/o error' ] ],
[ -106, [ 'EISCONN', 'socket is already connected' ] ],
[ -21, [ 'EISDIR', 'illegal operation on a directory' ] ],
[ -40, [ 'ELOOP', 'too many symbolic links encountered' ] ],
[ -24, [ 'EMFILE', 'too many open files' ] ],
[ -90, [ 'EMSGSIZE', 'message too long' ] ],
[ -36, [ 'ENAMETOOLONG', 'name too long' ] ],
[ -100, [ 'ENETDOWN', 'network is down' ] ],
[ -101, [ 'ENETUNREACH', 'network is unreachable' ] ],
[ -23, [ 'ENFILE', 'file table overflow' ] ],
[ -105, [ 'ENOBUFS', 'no buffer space available' ] ],
[ -19, [ 'ENODEV', 'no such device' ] ],
[ -2, [ 'ENOENT', 'no such file or directory' ] ],
[ -12, [ 'ENOMEM', 'not enough memory' ] ],
[ -64, [ 'ENONET', 'machine is not on the network' ] ],
[ -92, [ 'ENOPROTOOPT', 'protocol not available' ] ],
[ -28, [ 'ENOSPC', 'no space left on device' ] ],
[ -38, [ 'ENOSYS', 'function not implemented' ] ],
[ -107, [ 'ENOTCONN', 'socket is not connected' ] ],
[ -20, [ 'ENOTDIR', 'not a directory' ] ],
[ -39, [ 'ENOTEMPTY', 'directory not empty' ] ],
[ -88, [ 'ENOTSOCK', 'socket operation on non-socket' ] ],
[ -95, [ 'ENOTSUP', 'operation not supported on socket' ] ],
[ -75, [ 'EOVERFLOW', 'value too large for defined data type' ] ],
[ -1, [ 'EPERM', 'operation not permitted' ] ],
[ -32, [ 'EPIPE', 'broken pipe' ] ],
[ -71, [ 'EPROTO', 'protocol error' ] ],
[ -93, [ 'EPROTONOSUPPORT', 'protocol not supported' ] ],
[ -91, [ 'EPROTOTYPE', 'protocol wrong type for socket' ] ],
[ -34, [ 'ERANGE', 'result too large' ] ],
[ -30, [ 'EROFS', 'read-only file system' ] ],
[ -108, [ 'ESHUTDOWN', 'cannot send after transport endpoint shutdown' ] ],
[ -29, [ 'ESPIPE', 'invalid seek' ] ],
[ -3, [ 'ESRCH', 'no such process' ] ],
[ -110, [ 'ETIMEDOUT', 'connection timed out' ] ],
[ -26, [ 'ETXTBSY', 'text file is busy' ] ],
[ -18, [ 'EXDEV', 'cross-device link not permitted' ] ],
[ -4094, [ 'UNKNOWN', 'unknown error' ] ],
[ -4095, [ 'EOF', 'end of file' ] ],
[ -6, [ 'ENXIO', 'no such device or address' ] ],
[ -31, [ 'EMLINK', 'too many links' ] ],
[ -112, [ 'EHOSTDOWN', 'host is down' ] ],
[ -121, [ 'EREMOTEIO', 'remote I/O error' ] ],
[ -25, [ 'ENOTTY', 'inappropriate ioctl for device' ] ],
[ -4028, [ 'EFTYPE', 'inappropriate file type or format' ] ],
[ -84, [ 'EILSEQ', 'illegal byte sequence' ] ],
[ -94, [ 'ESOCKTNOSUPPORT', 'socket type not supported' ] ]
] as const);
}
export function getWin32SystemErrorMap() {
return new Map([
[ -4093, [ 'E2BIG', 'argument list too long' ] ],
[ -4092, [ 'EACCES', 'permission denied' ] ],
[ -4091, [ 'EADDRINUSE', 'address already in use' ] ],
[ -4090, [ 'EADDRNOTAVAIL', 'address not available' ] ],
[ -4089, [ 'EAFNOSUPPORT', 'address family not supported' ] ],
[ -4088, [ 'EAGAIN', 'resource temporarily unavailable' ] ],
[ -3000, [ 'EAI_ADDRFAMILY', 'address family not supported' ] ],
[ -3001, [ 'EAI_AGAIN', 'temporary failure' ] ],
[ -3002, [ 'EAI_BADFLAGS', 'bad ai_flags value' ] ],
[ -3013, [ 'EAI_BADHINTS', 'invalid value for hints' ] ],
[ -3003, [ 'EAI_CANCELED', 'request canceled' ] ],
[ -3004, [ 'EAI_FAIL', 'permanent failure' ] ],
[ -3005, [ 'EAI_FAMILY', 'ai_family not supported' ] ],
[ -3006, [ 'EAI_MEMORY', 'out of memory' ] ],
[ -3007, [ 'EAI_NODATA', 'no address' ] ],
[ -3008, [ 'EAI_NONAME', 'unknown node or service' ] ],
[ -3009, [ 'EAI_OVERFLOW', 'argument buffer overflow' ] ],
[ -3014, [ 'EAI_PROTOCOL', 'resolved protocol is unknown' ] ],
[ -3010, [ 'EAI_SERVICE', 'service not available for socket type' ] ],
[ -3011, [ 'EAI_SOCKTYPE', 'socket type not supported' ] ],
[ -4084, [ 'EALREADY', 'connection already in progress' ] ],
[ -4083, [ 'EBADF', 'bad file descriptor' ] ],
[ -4082, [ 'EBUSY', 'resource busy or locked' ] ],
[ -4081, [ 'ECANCELED', 'operation canceled' ] ],
[ -4080, [ 'ECHARSET', 'invalid Unicode character' ] ],
[ -4079, [ 'ECONNABORTED', 'software caused connection abort' ] ],
[ -4078, [ 'ECONNREFUSED', 'connection refused' ] ],
[ -4077, [ 'ECONNRESET', 'connection reset by peer' ] ],
[ -4076, [ 'EDESTADDRREQ', 'destination address required' ] ],
[ -4075, [ 'EEXIST', 'file already exists' ] ],
[ -4074, [ 'EFAULT', 'bad address in system call argument' ] ],
[ -4036, [ 'EFBIG', 'file too large' ] ],
[ -4073, [ 'EHOSTUNREACH', 'host is unreachable' ] ],
[ -4072, [ 'EINTR', 'interrupted system call' ] ],
[ -4071, [ 'EINVAL', 'invalid argument' ] ],
[ -4070, [ 'EIO', 'i/o error' ] ],
[ -4069, [ 'EISCONN', 'socket is already connected' ] ],
[ -4068, [ 'EISDIR', 'illegal operation on a directory' ] ],
[ -4067, [ 'ELOOP', 'too many symbolic links encountered' ] ],
[ -4066, [ 'EMFILE', 'too many open files' ] ],
[ -4065, [ 'EMSGSIZE', 'message too long' ] ],
[ -4064, [ 'ENAMETOOLONG', 'name too long' ] ],
[ -4063, [ 'ENETDOWN', 'network is down' ] ],
[ -4062, [ 'ENETUNREACH', 'network is unreachable' ] ],
[ -4061, [ 'ENFILE', 'file table overflow' ] ],
[ -4060, [ 'ENOBUFS', 'no buffer space available' ] ],
[ -4059, [ 'ENODEV', 'no such device' ] ],
[ -4058, [ 'ENOENT', 'no such file or directory' ] ],
[ -4057, [ 'ENOMEM', 'not enough memory' ] ],
[ -4056, [ 'ENONET', 'machine is not on the network' ] ],
[ -4035, [ 'ENOPROTOOPT', 'protocol not available' ] ],
[ -4055, [ 'ENOSPC', 'no space left on device' ] ],
[ -4054, [ 'ENOSYS', 'function not implemented' ] ],
[ -4053, [ 'ENOTCONN', 'socket is not connected' ] ],
[ -4052, [ 'ENOTDIR', 'not a directory' ] ],
[ -4051, [ 'ENOTEMPTY', 'directory not empty' ] ],
[ -4050, [ 'ENOTSOCK', 'socket operation on non-socket' ] ],
[ -4049, [ 'ENOTSUP', 'operation not supported on socket' ] ],
[ -4026, [ 'EOVERFLOW', 'value too large for defined data type' ] ],
[ -4048, [ 'EPERM', 'operation not permitted' ] ],
[ -4047, [ 'EPIPE', 'broken pipe' ] ],
[ -4046, [ 'EPROTO', 'protocol error' ] ],
[ -4045, [ 'EPROTONOSUPPORT', 'protocol not supported' ] ],
[ -4044, [ 'EPROTOTYPE', 'protocol wrong type for socket' ] ],
[ -4034, [ 'ERANGE', 'result too large' ] ],
[ -4043, [ 'EROFS', 'read-only file system' ] ],
[ -4042, [ 'ESHUTDOWN', 'cannot send after transport endpoint shutdown' ] ],
[ -4041, [ 'ESPIPE', 'invalid seek' ] ],
[ -4040, [ 'ESRCH', 'no such process' ] ],
[ -4039, [ 'ETIMEDOUT', 'connection timed out' ] ],
[ -4038, [ 'ETXTBSY', 'text file is busy' ] ],
[ -4037, [ 'EXDEV', 'cross-device link not permitted' ] ],
[ -4094, [ 'UNKNOWN', 'unknown error' ] ],
[ -4095, [ 'EOF', 'end of file' ] ],
[ -4033, [ 'ENXIO', 'no such device or address' ] ],
[ -4032, [ 'EMLINK', 'too many links' ] ],
[ -4031, [ 'EHOSTDOWN', 'host is down' ] ],
[ -4030, [ 'EREMOTEIO', 'remote I/O error' ] ],
[ -4029, [ 'ENOTTY', 'inappropriate ioctl for device' ] ],
[ -4028, [ 'EFTYPE', 'inappropriate file type or format' ] ],
[ -4027, [ 'EILSEQ', 'illegal byte sequence' ] ],
[ -4025, [ 'ESOCKTNOSUPPORT', 'socket type not supported' ] ]
] as const);
}
export function getPosixToWin32SystemErrorMap() {
const posixEntries = [...getPosixSystemErrorMap().entries()];
const win32Entries = [...getWin32SystemErrorMap().entries()];
const map: Map<PosixErrNo, Win32ErrNo> = new Map();
posixEntries.forEach(([code, val]) => {
const found = win32Entries.find(([_, v]) => v[0] === val[0]);
if (!found) console.error(val[0]);
else map.set(code, found[0]);
});
return map;
}
export function getPlatformSystemErrorFromPosix(posixErrNo: PosixErrNo) {
if (process.platform === 'win32') {
const win32errno = getPosixToWin32SystemErrorMap().get(posixErrNo)!;
return getWin32SystemErrorMap().get(win32errno);
} else {
return getPosixSystemErrorMap().get(posixErrNo);
}
}
export class SystemError extends Error {
constructor(errno: PosixErrNo, syscall?: string, errpath?: string) {
const [errname, errmsg] = getPlatformSystemErrorFromPosix(errno) ?? ['SystemError', 'Unknown system error'];
super(errmsg);
this.name = errname;
this.code = errname;
this.errno = errno;
if (syscall) this.syscall = syscall;
if (errpath) this.path = errpath;
}
errno?: number | undefined;
code?: string | undefined;
path?: string | undefined;
syscall?: string | undefined;
}
export class NotImplementedError extends Error {
constructor(thing: string, func: AnyCallable = NotImplementedError, overrideMsg: boolean = false) {
super(overrideMsg ? thing : `A polyfill for ${thing} is not yet implemented by bun-polyfills.`);
this.name = 'NotImplementedError';
Error.captureStackTrace(this, func);
}
}

View File

@@ -1,36 +1,36 @@
import streams from 'node:stream';
import type { SpawnOptions, FileBlob } from 'bun';
export const getter = <T>(obj: T, key: string | symbol, get: () => any, enumerable = false, configurable = true): void => {
Object.defineProperty(obj, key, { get, configurable, enumerable });
};
export const setter = <T>(obj: T, key: string | symbol, set: () => any, enumerable = false, configurable = true): void => {
Object.defineProperty(obj, key, { set, configurable, enumerable });
};
export const readonly = <T>(obj: T, key: string | symbol, value: unknown, enumerable = false, configurable = true): void => {
Object.defineProperty(obj, key, { value, configurable, enumerable });
};
export function streamToBuffer(stream: streams.Readable | streams.Duplex): Promise<Buffer> {
return new Promise((resolve, reject) => {
const buffers: Uint8Array[] = [];
stream.on("data", (chunk: Uint8Array) => buffers.push(chunk));
stream.on("end", () => resolve(Buffer.concat(buffers)));
stream.on("error", (err: Error) => reject(err));
});
}
export function isArrayBufferView(value: any): value is ArrayBufferView {
return value !== null && typeof value === 'object' &&
value.buffer instanceof ArrayBuffer && typeof value.byteLength === 'number' && typeof value.byteOffset === 'number';
}
export function isOptions(options: any): options is SpawnOptions.OptionsObject {
return options !== null && typeof options === 'object';
}
export function isFileBlob(blob: any): blob is FileBlob {
return blob instanceof Blob && Reflect.get(blob, 'readable') instanceof ReadableStream && typeof Reflect.get(blob, 'writer') === 'function';
}
import streams from 'node:stream';
import type { SpawnOptions, FileBlob } from 'bun';
export const getter = <T>(obj: T, key: string | symbol, get: () => any, enumerable = false, configurable = true): void => {
Object.defineProperty(obj, key, { get, configurable, enumerable });
};
export const setter = <T>(obj: T, key: string | symbol, set: () => any, enumerable = false, configurable = true): void => {
Object.defineProperty(obj, key, { set, configurable, enumerable });
};
export const readonly = <T>(obj: T, key: string | symbol, value: unknown, enumerable = false, configurable = true): void => {
Object.defineProperty(obj, key, { value, configurable, enumerable });
};
export function streamToBuffer(stream: streams.Readable | streams.Duplex): Promise<Buffer> {
return new Promise((resolve, reject) => {
const buffers: Uint8Array[] = [];
stream.on("data", (chunk: Uint8Array) => buffers.push(chunk));
stream.on("end", () => resolve(Buffer.concat(buffers)));
stream.on("error", (err: Error) => reject(err));
});
}
export function isArrayBufferView(value: any): value is ArrayBufferView {
return value !== null && typeof value === 'object' &&
value.buffer instanceof ArrayBuffer && typeof value.byteLength === 'number' && typeof value.byteOffset === 'number';
}
export function isOptions(options: any): options is SpawnOptions.OptionsObject {
return options !== null && typeof options === 'object';
}
export function isFileBlob(blob: any): blob is FileBlob {
return blob instanceof Blob && Reflect.get(blob, 'readable') instanceof ReadableStream && typeof Reflect.get(blob, 'writer') === 'function';
}

View File

@@ -1820,6 +1820,21 @@ declare module "bun" {
this: Server,
request: Errorlike,
) => Response | Promise<Response> | undefined | void | Promise<undefined>;
/**
* Uniquely identify a server instance with an ID
*
* ### When bun is started with the `--hot` flag
*
* This string will be used to hot reload the server without interrupting
* pending requests or websockets. If not provided, a value will be
* generated. To disable hot reloading, set this value to `null`.
*
* ### When bun is not started with the `--hot` flag
*
* This string will currently do nothing. But in the future it could be useful for logs or metrics.
*/
id?: string | null;
}
export type AnyFunction = (..._: any[]) => any;
@@ -2345,6 +2360,15 @@ declare module "bun" {
*
*/
readonly development: boolean;
/**
* An identifier of the server instance
*
* When bun is started with the `--hot` flag, this ID is used to hot reload the server without interrupting pending requests or websockets.
*
* When bun is not started with the `--hot` flag, this ID is currently unused.
*/
readonly id: string;
}
/**

1
packages/bun-usockets/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.h linguist-language=C

5
packages/bun-usockets/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
*.i
*.o
*.s
*.bc
*.ii

View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,3 @@
## Bun's fork of uSockets
Bun's TCP socket library is based on [uSockets](https://github.com/uNetworking/uSockets). Thanks to @uNetworkingAB for the great work!

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
// Script to update certdata.txt from NSS.
import { execFileSync } from 'node:child_process';
import { randomUUID } from 'node:crypto';
import { createWriteStream } from 'node:fs';
import { basename, dirname, join, relative } from 'node:path';
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { fileURLToPath } from 'node:url';
import { parseArgs } from 'node:util';
// Constants for NSS release metadata.
const kNSSVersion = 'version';
const kNSSDate = 'date';
const kFirefoxVersion = 'firefoxVersion';
const kFirefoxDate = 'firefoxDate';
const __filename = fileURLToPath(import.meta.url);
const now = new Date();
const formatDate = (d) => {
const iso = d.toISOString();
return iso.substring(0, iso.indexOf('T'));
};
const getCertdataURL = (version) => {
const tag = `NSS_${version.replaceAll('.', '_')}_RTM`;
const certdataURL = `https://hg.mozilla.org/projects/nss/raw-file/${tag}/lib/ckfw/builtins/certdata.txt`;
return certdataURL;
};
const normalizeTD = (text) => {
// Remove whitespace and any HTML tags.
return text?.trim().replace(/<.*?>/g, '');
};
const getReleases = (text) => {
const releases = [];
const tableRE = /<table [^>]+>([\S\s]*?)<\/table>/g;
const tableRowRE = /<tr ?[^>]*>([\S\s]*?)<\/tr>/g;
const tableHeaderRE = /<th ?[^>]*>([\S\s]*?)<\/th>/g;
const tableDataRE = /<td ?[^>]*>([\S\s]*?)<\/td>/g;
for (const table of text.matchAll(tableRE)) {
const columns = {};
const matches = table[1].matchAll(tableRowRE);
// First row has the table header.
let row = matches.next();
if (row.done) {
continue;
}
const headers = Array.from(row.value[1].matchAll(tableHeaderRE), (m) => m[1]);
if (headers.length > 0) {
for (let i = 0; i < headers.length; i++) {
if (/NSS version/i.test(headers[i])) {
columns[kNSSVersion] = i;
} else if (/Release.*from branch/i.test(headers[i])) {
columns[kNSSDate] = i;
} else if (/Firefox version/i.test(headers[i])) {
columns[kFirefoxVersion] = i;
} else if (/Firefox release date/i.test(headers[i])) {
columns[kFirefoxDate] = i;
}
}
}
// Filter out "NSS Certificate bugs" table.
if (columns[kNSSDate] === undefined) {
continue;
}
// Scrape releases.
row = matches.next();
while (!row.done) {
const cells = Array.from(row.value[1].matchAll(tableDataRE), (m) => m[1]);
const release = {};
release[kNSSVersion] = normalizeTD(cells[columns[kNSSVersion]]);
release[kNSSDate] = new Date(normalizeTD(cells[columns[kNSSDate]]));
release[kFirefoxVersion] = normalizeTD(cells[columns[kFirefoxVersion]]);
release[kFirefoxDate] = new Date(normalizeTD(cells[columns[kFirefoxDate]]));
releases.push(release);
row = matches.next();
}
}
return releases;
};
const getLatestVersion = async (releases) => {
const arrayNumberSortDescending = (x, y, i) => {
if (x[i] === undefined && y[i] === undefined) {
return 0;
} else if (x[i] === y[i]) {
return arrayNumberSortDescending(x, y, i + 1);
}
return (y[i] ?? 0) - (x[i] ?? 0);
};
const extractVersion = (t) => {
return t[kNSSVersion].split('.').map((n) => parseInt(n));
};
const releaseSorter = (x, y) => {
return arrayNumberSortDescending(extractVersion(x), extractVersion(y), 0);
};
// Return the most recent certadata.txt that exists on the server.
const sortedReleases = releases.sort(releaseSorter).filter(pastRelease);
for (const candidate of sortedReleases) {
const candidateURL = getCertdataURL(candidate[kNSSVersion]);
if (values.verbose) {
console.log(`Trying ${candidateURL}`);
}
const response = await fetch(candidateURL, { method: 'HEAD' });
if (response.ok) {
return candidate[kNSSVersion];
}
}
};
const pastRelease = (r) => {
return r[kNSSDate] < now;
};
const options = {
help: {
type: 'boolean',
},
file: {
short: 'f',
type: 'string',
},
verbose: {
short: 'v',
type: 'boolean',
},
};
const {
positionals,
values,
} = parseArgs({
allowPositionals: true,
options,
});
if (values.help) {
console.log(`Usage: ${basename(__filename)} [OPTION]... [VERSION]...`);
console.log();
console.log('Updates certdata.txt to NSS VERSION (most recent release by default).');
console.log('');
console.log(' -f, --file=FILE writes a commit message reflecting the change to the');
console.log(' specified FILE');
console.log(' -v, --verbose writes progress to stdout');
console.log(' --help display this help and exit');
process.exit(0);
}
const scheduleURL = 'https://wiki.mozilla.org/NSS:Release_Versions';
if (values.verbose) {
console.log(`Fetching NSS release schedule from ${scheduleURL}`);
}
const schedule = await fetch(scheduleURL);
if (!schedule.ok) {
console.error(`Failed to fetch ${scheduleURL}: ${schedule.status}: ${schedule.statusText}`);
process.exit(-1);
}
const scheduleText = await schedule.text();
const nssReleases = getReleases(scheduleText);
// Retrieve metadata for the NSS release being updated to.
const version = positionals[0] ?? await getLatestVersion(nssReleases);
const release = nssReleases.find((r) => {
return new RegExp(`^${version.replace('.', '\\.')}\\b`).test(r[kNSSVersion]);
});
if (!pastRelease(release)) {
console.warn(`Warning: NSS ${version} is not due to be released until ${formatDate(release[kNSSDate])}`);
}
if (values.verbose) {
console.log('Found NSS version:');
console.log(release);
}
// Fetch certdata.txt and overwrite the local copy.
const certdataURL = getCertdataURL(version);
if (values.verbose) {
console.log(`Fetching ${certdataURL}`);
}
const checkoutDir = dirname(__filename);
const certdata = await fetch(certdataURL);
const certdataFile = join(checkoutDir, 'certdata.txt');
if (!certdata.ok) {
console.error(`Failed to fetch ${certdataURL}: ${certdata.status}: ${certdata.statusText}`);
process.exit(-1);
}
if (values.verbose) {
console.log(`Writing ${certdataFile}`);
}
await pipeline(certdata.body, createWriteStream(certdataFile));
// Run generate-root-certs.pl to generate src/crypto/root_certs.h.
if (values.verbose) {
console.log('Running generate-root-certs.pl');
}
const opts = { encoding: 'utf8' };
const mkCABundleTool = join(checkoutDir, 'generate-root-certs.pl');
const mkCABundleOut = execFileSync(mkCABundleTool,
values.verbose ? [ '-v' ] : [],
opts);
if (values.verbose) {
console.log(mkCABundleOut);
}
// Determine certificates added and/or removed.
const certHeaderFile = relative(process.cwd(), join(checkoutDir, 'src', 'crypto', 'root_certs.h'));
const diff = execFileSync('git', [ 'diff-files', '-u', '--', certHeaderFile ], opts);
if (values.verbose) {
console.log(diff);
}
const certsAddedRE = /^\+\/\* (.*) \*\//gm;
const certsRemovedRE = /^-\/\* (.*) \*\//gm;
const added = [ ...diff.matchAll(certsAddedRE) ].map((m) => m[1]);
const removed = [ ...diff.matchAll(certsRemovedRE) ].map((m) => m[1]);
const commitMsg = [
`crypto: update root certificates to NSS ${release[kNSSVersion]}`,
'',
`This is the certdata.txt[0] from NSS ${release[kNSSVersion]}, released on ${formatDate(release[kNSSDate])}.`,
'',
`This is the version of NSS that ${release[kFirefoxDate] < now ? 'shipped' : 'will ship'} in Firefox ${release[kFirefoxVersion]} on`,
`${formatDate(release[kFirefoxDate])}.`,
'',
];
if (added.length > 0) {
commitMsg.push('Certificates added:');
commitMsg.push(...added.map((cert) => `- ${cert}`));
commitMsg.push('');
}
if (removed.length > 0) {
commitMsg.push('Certificates removed:');
commitMsg.push(...removed.map((cert) => `- ${cert}`));
commitMsg.push('');
}
commitMsg.push(`[0] ${certdataURL}`);
const delimiter = randomUUID();
const properties = [
`NEW_VERSION=${release[kNSSVersion]}`,
`COMMIT_MSG<<${delimiter}`,
...commitMsg,
delimiter,
'',
].join('\n');
if (values.verbose) {
console.log(properties);
}
const propertyFile = values.file;
if (propertyFile !== undefined) {
console.log(`Writing to ${propertyFile}`);
await pipeline(Readable.from(properties), createWriteStream(propertyFile));
}

View File

@@ -0,0 +1,356 @@
#!/usr/bin/perl -w
# ***************************************************************************
# * _ _ ____ _
# * Project ___| | | | _ \| |
# * / __| | | | |_) | |
# * | (__| |_| | _ <| |___
# * \___|\___/|_| \_\_____|
# *
# * Copyright (C) 1998 - 2014, Daniel Stenberg, <daniel@haxx.se>, et al.
# *
# * This software is licensed as described in the file COPYING, which
# * you should have received as part of this distribution. The terms
# * are also available at http://curl.haxx.se/docs/copyright.html.
# *
# * You may opt to use, copy, modify, merge, publish, distribute and/or sell
# * copies of the Software, and permit persons to whom the Software is
# * furnished to do so, under the terms of the COPYING file.
# *
# * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
# * KIND, either express or implied.
# *
# ***************************************************************************
# This Perl script creates a fresh ca-bundle.crt file for use with libcurl.
# It downloads certdata.txt from Mozilla's source tree (see URL below),
# then parses certdata.txt and extracts CA Root Certificates into PEM format.
# These are then processed with the OpenSSL commandline tool to produce the
# final ca-bundle.crt file.
# The script is based on the parse-certs script written by Roland Krikava.
# This Perl script works on almost any platform since its only external
# dependency is the OpenSSL commandline tool for optional text listing.
# Hacked by Guenter Knauf.
#
use File::Basename 'dirname';
use Getopt::Std;
use MIME::Base64;
use strict;
use vars qw($opt_h $opt_i $opt_l $opt_p $opt_q $opt_s $opt_t $opt_v $opt_w);
use List::Util;
use Text::Wrap;
# If the OpenSSL commandline is not in search path you can configure it here!
my $openssl = 'openssl';
my $version = '1.25';
$opt_w = 72; # default base64 encoded lines length
# default cert types to include in the output (default is to include CAs which may issue SSL server certs)
my $default_mozilla_trust_purposes = "SERVER_AUTH";
my $default_mozilla_trust_levels = "TRUSTED_DELEGATOR";
$opt_p = $default_mozilla_trust_purposes . ":" . $default_mozilla_trust_levels;
my @valid_mozilla_trust_purposes = (
"DIGITAL_SIGNATURE",
"NON_REPUDIATION",
"KEY_ENCIPHERMENT",
"DATA_ENCIPHERMENT",
"KEY_AGREEMENT",
"KEY_CERT_SIGN",
"CRL_SIGN",
"SERVER_AUTH",
"CLIENT_AUTH",
"CODE_SIGNING",
"EMAIL_PROTECTION",
"IPSEC_END_SYSTEM",
"IPSEC_TUNNEL",
"IPSEC_USER",
"TIME_STAMPING",
"STEP_UP_APPROVED"
);
my @valid_mozilla_trust_levels = (
"TRUSTED_DELEGATOR", # CAs
"NOT_TRUSTED", # Don't trust these certs.
"MUST_VERIFY_TRUST", # This explicitly tells us that it ISN'T a CA but is otherwise ok. In other words, this should tell the app to ignore any other sources that claim this is a CA.
"TRUSTED" # This cert is trusted, but only for itself and not for delegates (i.e. it is not a CA).
);
my $default_signature_algorithms = $opt_s = "MD5";
my @valid_signature_algorithms = (
"MD5",
"SHA1",
"SHA256",
"SHA384",
"SHA512"
);
$0 =~ s@.*(/|\\)@@;
$Getopt::Std::STANDARD_HELP_VERSION = 1;
getopts('bd:fhilnp:qs:tuvw:');
if ($opt_i) {
print ("=" x 78 . "\n");
print "Script Version : $version\n";
print "Perl Version : $]\n";
print "Operating System Name : $^O\n";
print "Getopt::Std.pm Version : ${Getopt::Std::VERSION}\n";
print "MIME::Base64.pm Version : ${MIME::Base64::VERSION}\n";
print ("=" x 78 . "\n");
}
sub HELP_MESSAGE() {
print "Usage:\t${0} [-i] [-l] [-p<purposes:levels>] [-q] [-s<algorithms>] [-t] [-v] [-w<l>] [<outputfile>]\n";
print "\t-i\tprint version info about used modules\n";
print "\t-l\tprint license info about certdata.txt\n";
print wrap("\t","\t\t", "-p\tlist of Mozilla trust purposes and levels for certificates to include in output. Takes the form of a comma separated list of purposes, a colon, and a comma separated list of levels. (default: $default_mozilla_trust_purposes:$default_mozilla_trust_levels)"), "\n";
print "\t\t Valid purposes are:\n";
print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_mozilla_trust_purposes ) ), "\n";
print "\t\t Valid levels are:\n";
print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_mozilla_trust_levels ) ), "\n";
print "\t-q\tbe really quiet (no progress output at all)\n";
print wrap("\t","\t\t", "-s\tcomma separated list of certificate signatures/hashes to output in plain text mode. (default: $default_signature_algorithms)\n");
print "\t\t Valid signature algorithms are:\n";
print wrap("\t\t ","\t\t ", join( ", ", "ALL", @valid_signature_algorithms ) ), "\n";
print "\t-t\tinclude plain text listing of certificates\n";
print "\t-v\tbe verbose and print out processed CAs\n";
print "\t-w <l>\twrap base64 output lines after <l> chars (default: ${opt_w})\n";
exit;
}
sub VERSION_MESSAGE() {
print "${0} version ${version} running Perl ${]} on ${^O}\n";
}
HELP_MESSAGE() if ($opt_h);
sub report($@) {
my $output = shift;
print STDERR $output . "\n" unless $opt_q;
}
sub is_in_list($@) {
my $target = shift;
return defined(List::Util::first { $target eq $_ } @_);
}
# Parses $param_string as a case insensitive comma separated list with optional whitespace
# validates that only allowed parameters are supplied
sub parse_csv_param($$@) {
my $description = shift;
my $param_string = shift;
my @valid_values = @_;
my @values = map {
s/^\s+//; # strip leading spaces
s/\s+$//; # strip trailing spaces
uc $_ # return the modified string as upper case
} split( ',', $param_string );
# Find all values which are not in the list of valid values or "ALL"
my @invalid = grep { !is_in_list($_,"ALL",@valid_values) } @values;
if ( scalar(@invalid) > 0 ) {
# Tell the user which parameters were invalid and print the standard help message which will exit
print "Error: Invalid ", $description, scalar(@invalid) == 1 ? ": " : "s: ", join( ", ", map { "\"$_\"" } @invalid ), "\n";
HELP_MESSAGE();
}
@values = @valid_values if ( is_in_list("ALL",@values) );
return @values;
}
if ( $opt_p !~ m/:/ ) {
print "Error: Mozilla trust identifier list must include both purposes and levels\n";
HELP_MESSAGE();
}
(my $included_mozilla_trust_purposes_string, my $included_mozilla_trust_levels_string) = split( ':', $opt_p );
my @included_mozilla_trust_purposes = parse_csv_param( "trust purpose", $included_mozilla_trust_purposes_string, @valid_mozilla_trust_purposes );
my @included_mozilla_trust_levels = parse_csv_param( "trust level", $included_mozilla_trust_levels_string, @valid_mozilla_trust_levels );
my @included_signature_algorithms = parse_csv_param( "signature algorithm", $opt_s, @valid_signature_algorithms );
sub should_output_cert(%) {
my %trust_purposes_by_level = @_;
foreach my $level (@included_mozilla_trust_levels) {
# for each level we want to output, see if any of our desired purposes are included
return 1 if ( defined( List::Util::first { is_in_list( $_, @included_mozilla_trust_purposes ) } @{$trust_purposes_by_level{$level}} ) );
}
return 0;
}
my $crt = $ARGV[0] || dirname(__FILE__) . '/src/crypto/root_certs.h';
my $txt = dirname(__FILE__) . '/certdata.txt';
my $stdout = $crt eq '-';
if( $stdout ) {
open(CRT, '> -') or die "Couldn't open STDOUT: $!\n";
} else {
open(CRT,">$crt.~") or die "Couldn't open $crt.~: $!\n";
}
my $caname;
my $certnum = 0;
my $skipnum = 0;
my $start_of_cert = 0;
open(TXT,"$txt") or die "Couldn't open $txt: $!\n";
print CRT "// Maintaining the root certificates \n";
print CRT "//\n";
print CRT "// `src/crypto/root_certs.h` contains a compiled-in set of root certificates used as trust anchors\n";
print CRT "// for TLS certificate validation.\n";
print CRT "//\n";
print CRT "// The certificates come from Mozilla, specifically NSS's `certdata.txt` file.\n";
print CRT "//\n";
print CRT "// The PEM encodings of the certificates are converted to C strings, and committed\n";
print CRT "// in `src/crypto/root_certs.h`.\n";
print CRT "//\n";
print CRT "// When to update\n";
print CRT "//\n";
print CRT "// Root certificates should be updated sometime after Mozilla makes an NSS release,\n";
print CRT "// check the NSS release schedule in https://wiki.mozilla.org/NSS:Release_Versions.\n";
print CRT "//\n";
print CRT "// Process\n";
print CRT "//\n";
print CRT "// The `generate-root-certs.js` script automates the update of\n";
print CRT "// the root certificates, including:\n";
print CRT "//\n";
print CRT "// * Downloading `certdata.txt` from Mozilla's source control repository.\n";
print CRT "// * Running `generate-ca-bundle.pl` to convert the certificates and generate\n";
print CRT "// `src/crypto/root_certs.h`.\n";
print CRT "// * Using `git diff-files` to determine which certificate have been added and/or\n";
print CRT "// removed.\n";
print CRT "// \n";
print CRT "#include \"libusockets.h\"\n";
print CRT "static struct us_cert_string_t root_certs[] = {\n";
while (<TXT>) {
if (/\*\*\*\*\* BEGIN LICENSE BLOCK \*\*\*\*\*/) {
print CRT;
print if ($opt_l);
while (<TXT>) {
print CRT;
print if ($opt_l);
last if (/\*\*\*\*\* END LICENSE BLOCK \*\*\*\*\*/);
}
}
next if /^#|^\s*$/;
chomp;
if (/^CVS_ID\s+\"(.*)\"/) {
print CRT "/* $1 */\n";
}
# this is a match for the start of a certificate
if (/^CKA_CLASS CK_OBJECT_CLASS CKO_CERTIFICATE/) {
$start_of_cert = 1
}
if ($start_of_cert && /^CKA_LABEL UTF8 \"(.*)\"/) {
$caname = $1;
}
my %trust_purposes_by_level;
if ($start_of_cert && /^CKA_VALUE MULTILINE_OCTAL/) {
my $data;
while (<TXT>) {
last if (/^END/);
chomp;
my @octets = split(/\\/);
shift @octets;
for (@octets) {
$data .= chr(oct);
}
}
# scan forwards until the trust part
while (<TXT>) {
last if (/^CKA_CLASS CK_OBJECT_CLASS CKO_NSS_TRUST/);
chomp;
}
# now scan the trust part to determine how we should trust this cert
while (<TXT>) {
last if (/^#/);
if (/^CKA_TRUST_([A-Z_]+)\s+CK_TRUST\s+CKT_NSS_([A-Z_]+)\s*$/) {
if ( !is_in_list($1,@valid_mozilla_trust_purposes) ) {
report "Warning: Unrecognized trust purpose for cert: $caname. Trust purpose: $1. Trust Level: $2";
} elsif ( !is_in_list($2,@valid_mozilla_trust_levels) ) {
report "Warning: Unrecognized trust level for cert: $caname. Trust purpose: $1. Trust Level: $2";
} else {
push @{$trust_purposes_by_level{$2}}, $1;
}
}
}
if ( !should_output_cert(%trust_purposes_by_level) ) {
$skipnum ++;
} elsif ($caname =~ /TrustCor/) {
$skipnum ++;
} else {
my $encoded = MIME::Base64::encode_base64($data, '');
my $encoded_len_calc = $encoded;
$encoded_len_calc =~ s/(.{1,${opt_w}})/$1\n/g;
my $cert_len = length($encoded_len_calc) + 53;
$encoded =~ s/(.{1,${opt_w}})/"$1\\n"\n/g;
my $pem = "{.str=\"-----BEGIN CERTIFICATE-----\\n\"\n"
. $encoded
. "\"-----END CERTIFICATE-----\",.len="
. $cert_len
."},\n";
print CRT "\n/* $caname */\n";
my $maxStringLength = length($caname);
if ($opt_t) {
foreach my $key (keys %trust_purposes_by_level) {
my $string = $key . ": " . join(", ", @{$trust_purposes_by_level{$key}});
$maxStringLength = List::Util::max( length($string), $maxStringLength );
print CRT $string . "\n";
}
}
if (!$opt_t) {
print CRT $pem;
} else {
my $pipe = "";
foreach my $hash (@included_signature_algorithms) {
$pipe = "|$openssl x509 -" . $hash . " -fingerprint -noout -inform PEM";
if (!$stdout) {
$pipe .= " >> $crt.~";
close(CRT) or die "Couldn't close $crt.~: $!";
}
open(TMP, $pipe) or die "Couldn't open openssl pipe: $!";
print TMP $pem;
close(TMP) or die "Couldn't close openssl pipe: $!";
if (!$stdout) {
open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!";
}
}
$pipe = "|$openssl x509 -text -inform PEM";
if (!$stdout) {
$pipe .= " >> $crt.~";
close(CRT) or die "Couldn't close $crt.~: $!";
}
open(TMP, $pipe) or die "Couldn't open openssl pipe: $!";
print TMP $pem;
close(TMP) or die "Couldn't close openssl pipe: $!";
if (!$stdout) {
open(CRT, ">>$crt.~") or die "Couldn't open $crt.~: $!";
}
}
report "Parsing: $caname" if ($opt_v);
$certnum ++;
$start_of_cert = 0;
}
}
}
print CRT "};\n";
close(TXT) or die "Couldn't close $txt: $!\n";
close(CRT) or die "Couldn't close $crt.~: $!\n";
unless( $stdout ) {
rename "$crt.~", $crt or die "Failed to rename $crt.~ to $crt: $!\n";
}
report "Done ($certnum CA certs processed, $skipnum skipped).";

View File

@@ -0,0 +1,51 @@
#!/bin/bash
set -eo pipefail
function gen_cert {
local path=$1
local CN=$2
local ca_path=$3
local ca_name=${4:-ca}
mkdir -p ${path}
openssl genrsa -out ${path}/${CN}_key.pem 2048 >/dev/null
echo "generated ${path}/${CN}_key.pem"
openssl req -new -sha256 \
-key ${path}/${CN}_key.pem \
-subj "/O=uNetworking/O=uSockets/CN=${CN}" \
-reqexts SAN \
-config <(cat /etc/ssl/openssl.cnf \
<(printf "\n[SAN]\nsubjectAltName=DNS:localhost,DNS:127.0.0.1")) \
-out ${path}/${CN}.csr &>/dev/null
if [ -z "${ca_path}" ]; then
# self-signed
openssl x509 -req -in ${path}/${CN}.csr \
-signkey ${path}/${CN}_key.pem -days 365 -sha256 \
-outform PEM -out ${path}/${CN}_crt.pem &>/dev/null
else
openssl x509 -req -in ${path}/${CN}.csr \
-CA ${ca_path}/${ca_name}_crt.pem -CAkey ${ca_path}/${ca_name}_key.pem \
-CAcreateserial -days 365 -sha256 \
-outform PEM -out ${path}/${CN}_crt.pem &>/dev/null
fi
rm -f ${path}/${CN}.csr
echo "generated ${path}/${CN}_crt.pem"
}
# main
certs=${1:-"/tmp/certs"}
gen_cert "${certs}" "valid_ca"
gen_cert "${certs}" "valid_server" "${certs}" "valid_ca"
gen_cert "${certs}" "valid_client" "${certs}" "valid_ca"
gen_cert "${certs}" "invalid_ca"
gen_cert "${certs}" "invalid_client" "${certs}" "invalid_ca"
gen_cert "${certs}" "selfsigned_client"

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -0,0 +1,174 @@
# libusockets.h
This is the only header you include. Following documentation has been extracted from this header. It may be outdated, go read the header directly for up-to-date documentation.
These interfaces are "beta" and subject to smaller changes. Last updated **2019-06-11**.
# A quick note on compilation
Major differences in performance can be seen based solely on compiler and/or linker options. Important is to compile with some kind of link-time-optimization mode, preferably with static linking of this library such as including all C source files in the user program build step itself. Proper compilation and linking can lead to over 25% performance increase (in my case, YMMV).
# Cross-platform benchmarks
While the library is compatible with many platforms, Linux in particular is the preferred production system. Benchmarking has been done on Windows, Linux and macOS where Linux clearly stood out as significant winner. Windows performed about half that of Linux and macOS was not much better than Windows. Do run your production systems on Linux.
# us_loop_t - The root per-thread resource and callback emitter
```c
/* Returns a new event loop with user data extension */
WIN32_EXPORT struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t *loop), void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop), unsigned int ext_size);
/* Frees the loop immediately */
WIN32_EXPORT void us_loop_free(struct us_loop_t *loop);
/* Returns the loop user data extension */
WIN32_EXPORT void *us_loop_ext(struct us_loop_t *loop);
/* Blocks the calling thread and drives the event loop until no more non-fallthrough polls are scheduled */
WIN32_EXPORT void us_loop_run(struct us_loop_t *loop);
/* Signals the loop from any thread to wake up and execute its wakeup handler from the loop's own running thread.
* This is the only fully thread-safe function and serves as the basis for thread safety */
WIN32_EXPORT void us_wakeup_loop(struct us_loop_t *loop);
/* Hook up timers in existing loop */
WIN32_EXPORT void us_loop_integrate(struct us_loop_t *loop);
/* Returns the loop iteration number */
WIN32_EXPORT long long us_loop_iteration_number(struct us_loop_t *loop);
```
# us_socket_context_t - The per-behavior group of networking sockets
```c
struct us_socket_context_options_t {
const char *key_file_name;
const char *cert_file_name;
const char *passphrase;
const char *dh_params_file_name;
const char *ca_file_name;
const char *ssl_ciphers;
int ssl_prefer_low_memory_usage;
};
/* A socket context holds shared callbacks and user data extension for associated sockets */
WIN32_EXPORT struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *loop, int ext_size, struct us_socket_context_options_t options);
/* Delete resources allocated at creation time. */
WIN32_EXPORT void us_socket_context_free(int ssl, struct us_socket_context_t *context);
/* Setters of various async callbacks */
WIN32_EXPORT void us_socket_context_on_open(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length));
WIN32_EXPORT void us_socket_context_on_close(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_close)(struct us_socket_t *s));
WIN32_EXPORT void us_socket_context_on_data(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length));
WIN32_EXPORT void us_socket_context_on_writable(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_writable)(struct us_socket_t *s));
WIN32_EXPORT void us_socket_context_on_timeout(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_timeout)(struct us_socket_t *s));
/* Emitted when a socket has been half-closed */
WIN32_EXPORT void us_socket_context_on_end(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_end)(struct us_socket_t *s));
/* Returns user data extension for this socket context */
WIN32_EXPORT void *us_socket_context_ext(int ssl, struct us_socket_context_t *context);
/* Listen for connections. Acts as the main driving cog in a server. Will call set async callbacks. */
WIN32_EXPORT struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size);
/* listen_socket.c/.h */
WIN32_EXPORT void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls);
/* Land in on_open or on_close or return null or return socket */
WIN32_EXPORT struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size);
/* Returns the loop for this socket context. */
WIN32_EXPORT struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context);
/* Invalidates passed socket, returning a new resized socket which belongs to a different socket context.
* Used mainly for "socket upgrades" such as when transitioning from HTTP to WebSocket. */
WIN32_EXPORT struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int ext_size);
/* Create a child socket context which acts much like its own socket context with its own callbacks yet still relies on the
* parent socket context for some shared resources. Child socket contexts should be used together with socket adoptions and nothing else. */
WIN32_EXPORT struct us_socket_context_t *us_create_child_socket_context(int ssl, struct us_socket_context_t *context, int context_ext_size);
```
# us_socket_t - The network connection (SSL or non-SSL)
```c
/* Write up to length bytes of data. Returns actual bytes written. Will call the on_writable callback of active socket context on failure to write everything off in one go.
* Set hint msg_more if you have more immediate data to write. */
WIN32_EXPORT int us_socket_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more);
/* Set a low precision, high performance timer on a socket. A socket can only have one single active timer at any given point in time. Will remove any such pre set timer */
WIN32_EXPORT void us_socket_timeout(int ssl, struct us_socket_t *s, unsigned int seconds);
/* Return the user data extension of this socket */
WIN32_EXPORT void *us_socket_ext(int ssl, struct us_socket_t *s);
/* Return the socket context of this socket */
WIN32_EXPORT struct us_socket_context_t *us_socket_context(int ssl, struct us_socket_t *s);
/* Withdraw any msg_more status and flush any pending data */
WIN32_EXPORT void us_socket_flush(int ssl, struct us_socket_t *s);
/* Shuts down the connection by sending FIN and/or close_notify */
WIN32_EXPORT void us_socket_shutdown(int ssl, struct us_socket_t *s);
/* Returns whether the socket has been shut down or not */
WIN32_EXPORT int us_socket_is_shut_down(int ssl, struct us_socket_t *s);
/* Returns whether this socket has been closed. Only valid if memory has not yet been released. */
WIN32_EXPORT int us_socket_is_closed(int ssl, struct us_socket_t *s);
/* Immediately closes the socket */
WIN32_EXPORT struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s);
/* Copy remote (IP) address of socket, or fail with zero length. */
WIN32_EXPORT void us_socket_remote_address(int ssl, struct us_socket_t *s, char *buf, int *length);
```
# Low level components
## us_timer_t - High cost (very expensive resource) timers
**NOTE:** Many slow servers use one timer per socket. That is incredibly inefficient and so uSockets will only use one single us_timer_t per every one us_loop_t. A similar design is utilized in the Linux kernel and is how you should think of timers yourself.
```c
/* Create a new high precision, low performance timer. May fail and return null */
WIN32_EXPORT struct us_timer_t *us_create_timer(struct us_loop_t *loop, int fallthrough, unsigned int ext_size);
/* Returns user data extension for this timer */
WIN32_EXPORT void *us_timer_ext(struct us_timer_t *timer);
/* */
WIN32_EXPORT void us_timer_close(struct us_timer_t *timer);
/* Arm a timer with a delay from now and eventually a repeat delay.
* Specify 0 as repeat delay to disable repeating. Specify both 0 to disarm. */
WIN32_EXPORT void us_timer_set(struct us_timer_t *timer, void (*cb)(struct us_timer_t *t), int ms, int repeat_ms);
/* Returns the loop for this timer */
WIN32_EXPORT struct us_loop_t *us_timer_loop(struct us_timer_t *t);
```
## us_poll_t - The eventing foundation of a socket or anything that has a file descriptor
```c
/* A fallthrough poll does not keep the loop running, it falls through */
WIN32_EXPORT struct us_poll_t *us_create_poll(struct us_loop_t *loop, int fallthrough, unsigned int ext_size);
/* After stopping a poll you must manually free the memory */
WIN32_EXPORT void us_poll_free(struct us_poll_t *p, struct us_loop_t *loop);
/* Associate this poll with a socket descriptor and poll type */
WIN32_EXPORT void us_poll_init(struct us_poll_t *p, LIBUS_SOCKET_DESCRIPTOR fd, int poll_type);
/* Start, change and stop polling for events */
WIN32_EXPORT void us_poll_start(struct us_poll_t *p, struct us_loop_t *loop, int events);
WIN32_EXPORT void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events);
WIN32_EXPORT void us_poll_stop(struct us_poll_t *p, struct us_loop_t *loop);
/* Return what events we are polling for */
WIN32_EXPORT int us_poll_events(struct us_poll_t *p);
/* Returns the user data extension of this poll */
WIN32_EXPORT void *us_poll_ext(struct us_poll_t *p);
/* Get associated socket descriptor from a poll */
WIN32_EXPORT LIBUS_SOCKET_DESCRIPTOR us_poll_fd(struct us_poll_t *p);
/* Resize an active poll */
WIN32_EXPORT struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop, unsigned int ext_size);
```

View File

@@ -0,0 +1,4 @@
module bun-usockets {
header "src/libusockets.h"
export *
}

View File

@@ -0,0 +1,738 @@
/*
* Authored by Alex Hultman, 2018-2021.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Todo: this file should lie in networking/bsd.c */
#define __APPLE_USE_RFC_3542
#include "libusockets.h"
#include "internal/internal.h"
#include <stdio.h>
#include <stdlib.h>
#ifndef _WIN32
//#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#endif
/* Internal structure of packet buffer */
struct us_internal_udp_packet_buffer {
#if defined(_WIN32) || defined(__APPLE__)
char *buf[LIBUS_UDP_MAX_NUM];
size_t len[LIBUS_UDP_MAX_NUM];
struct sockaddr_storage addr[LIBUS_UDP_MAX_NUM];
#else
struct mmsghdr msgvec[LIBUS_UDP_MAX_NUM];
struct iovec iov[LIBUS_UDP_MAX_NUM];
struct sockaddr_storage addr[LIBUS_UDP_MAX_NUM];
char control[LIBUS_UDP_MAX_NUM][256];
#endif
};
/* We need to emulate sendmmsg, recvmmsg on platform who don't have it */
int bsd_sendmmsg(LIBUS_SOCKET_DESCRIPTOR fd, void *msgvec, unsigned int vlen, int flags) {
#if defined(__APPLE__)
struct mmsghdr {
struct msghdr msg_hdr; /* Message header */
unsigned int msg_len; /* Number of bytes transmitted */
};
struct mmsghdr *hdrs = (struct mmsghdr *) msgvec;
for (int i = 0; i < vlen; i++) {
int ret = sendmsg(fd, &hdrs[i].msg_hdr, flags);
if (ret == -1) {
if (i) {
return i;
} else {
return -1;
}
} else {
hdrs[i].msg_len = ret;
}
}
return vlen;
#elif defined(_WIN32)
struct us_internal_udp_packet_buffer *packet_buffer = (struct us_internal_udp_packet_buffer *) msgvec;
/* Let's just use sendto here */
/* Winsock does not have sendmsg, while macOS has, however, we simply use sendto since both macOS and Winsock has it.
* Besides, you should use Linux either way to get best performance with the sendmmsg */
// while we do not get error, send next
for (int i = 0; i < LIBUS_UDP_MAX_NUM; i++) {
// need to support ipv6 addresses also!
int ret = sendto(fd, packet_buffer->buf[i], packet_buffer->len[i], flags, (struct sockaddr *)&packet_buffer->addr[i], sizeof(struct sockaddr_in));
if (ret == -1) {
// if we fail then we need to buffer up, no that's not our problem
// we do need to register poll out though and have a callback for it
return i;
}
//printf("sendto: %d\n", ret);
}
return LIBUS_UDP_MAX_NUM; // one message
#else
return sendmmsg(fd, (struct mmsghdr *)msgvec, vlen, flags | MSG_NOSIGNAL);
#endif
}
int bsd_recvmmsg(LIBUS_SOCKET_DESCRIPTOR fd, void *msgvec, unsigned int vlen, int flags, void *timeout) {
#if defined(_WIN32) || defined(__APPLE__)
struct us_internal_udp_packet_buffer *packet_buffer = (struct us_internal_udp_packet_buffer *) msgvec;
for (int i = 0; i < LIBUS_UDP_MAX_NUM; i++) {
socklen_t addr_len = sizeof(struct sockaddr_storage);
int ret = recvfrom(fd, packet_buffer->buf[i], LIBUS_UDP_MAX_SIZE, flags, (struct sockaddr *)&packet_buffer->addr[i], &addr_len);
if (ret == -1) {
return i;
}
packet_buffer->len[i] = ret;
}
return LIBUS_UDP_MAX_NUM;
#else
// we need to set controllen for ip packet
for (int i = 0; i < vlen; i++) {
((struct mmsghdr *)msgvec)[i].msg_hdr.msg_controllen = 256;
}
return recvmmsg(fd, (struct mmsghdr *)msgvec, vlen, flags, 0);
#endif
}
// this one is needed for knowing the destination addr of udp packet
// an udp socket can only bind to one port, and that port never changes
// this function returns ONLY the IP address, not any port
int bsd_udp_packet_buffer_local_ip(void *msgvec, int index, char *ip) {
#if defined(_WIN32) || defined(__APPLE__)
return 0; // not supported
#else
struct msghdr *mh = &((struct mmsghdr *) msgvec)[index].msg_hdr;
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(mh); cmsg != NULL; cmsg = CMSG_NXTHDR(mh, cmsg)) {
// ipv6 or ipv4
if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_PKTINFO) {
struct in_pktinfo *pi = (struct in_pktinfo *) CMSG_DATA(cmsg);
memcpy(ip, &pi->ipi_addr, 4);
return 4;
}
if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) {
struct in6_pktinfo *pi6 = (struct in6_pktinfo *) CMSG_DATA(cmsg);
memcpy(ip, &pi6->ipi6_addr, 16);
return 16;
}
}
return 0; // no length
#endif
}
char *bsd_udp_packet_buffer_peer(void *msgvec, int index) {
#if defined(_WIN32) || defined(__APPLE__)
struct us_internal_udp_packet_buffer *packet_buffer = (struct us_internal_udp_packet_buffer *) msgvec;
return (char *)&packet_buffer->addr[index];
#else
return ((struct mmsghdr *) msgvec)[index].msg_hdr.msg_name;
#endif
}
char *bsd_udp_packet_buffer_payload(void *msgvec, int index) {
#if defined(_WIN32) || defined(__APPLE__)
struct us_internal_udp_packet_buffer *packet_buffer = (struct us_internal_udp_packet_buffer *) msgvec;
return packet_buffer->buf[index];
#else
return ((struct mmsghdr *) msgvec)[index].msg_hdr.msg_iov[0].iov_base;
#endif
}
int bsd_udp_packet_buffer_payload_length(void *msgvec, int index) {
#if defined(_WIN32) || defined(__APPLE__)
struct us_internal_udp_packet_buffer *packet_buffer = (struct us_internal_udp_packet_buffer *) msgvec;
return packet_buffer->len[index];
#else
return ((struct mmsghdr *) msgvec)[index].msg_len;
#endif
}
void bsd_udp_buffer_set_packet_payload(struct us_udp_packet_buffer_t *send_buf, int index, int offset, void *payload, int length, void *peer_addr) {
#if defined(_WIN32) || defined(__APPLE__)
struct us_internal_udp_packet_buffer *packet_buffer = (struct us_internal_udp_packet_buffer *) send_buf;
memcpy(packet_buffer->buf[index], payload, length);
memcpy(&packet_buffer->addr[index], peer_addr, sizeof(struct sockaddr_storage));
packet_buffer->len[index] = length;
#else
//printf("length: %d, offset: %d\n", length, offset);
struct mmsghdr *ss = (struct mmsghdr *) send_buf;
// copy the peer address
memcpy(ss[index].msg_hdr.msg_name, peer_addr, /*ss[index].msg_hdr.msg_namelen*/ sizeof(struct sockaddr_in));
// set control length to 0
ss[index].msg_hdr.msg_controllen = 0;
// copy the payload
ss[index].msg_hdr.msg_iov->iov_len = length + offset;
memcpy(((char *) ss[index].msg_hdr.msg_iov->iov_base) + offset, payload, length);
#endif
}
/* The maximum UDP payload size is 64kb, but in IPV6 you can have jumbopackets larger than so.
* We do not support those jumbo packets currently, but will safely ignore them.
* Any sane sender would assume we don't support them if we consistently drop them.
* Therefore a udp_packet_buffer_t will be 64 MB in size (64kb * 1024). */
void *bsd_create_udp_packet_buffer() {
#if defined(_WIN32) || defined(__APPLE__)
struct us_internal_udp_packet_buffer *b = malloc(sizeof(struct us_internal_udp_packet_buffer) + LIBUS_UDP_MAX_SIZE * LIBUS_UDP_MAX_NUM);
for (int i = 0; i < LIBUS_UDP_MAX_NUM; i++) {
b->buf[i] = ((char *) b) + sizeof(struct us_internal_udp_packet_buffer) + LIBUS_UDP_MAX_SIZE * i;
}
return (struct us_udp_packet_buffer_t *) b;
#else
/* Allocate 64kb times 1024 */
struct us_internal_udp_packet_buffer *b = malloc(sizeof(struct us_internal_udp_packet_buffer) + LIBUS_UDP_MAX_SIZE * LIBUS_UDP_MAX_NUM);
for (int n = 0; n < LIBUS_UDP_MAX_NUM; ++n) {
b->iov[n].iov_base = &((char *) (b + 1))[n * LIBUS_UDP_MAX_SIZE];
b->iov[n].iov_len = LIBUS_UDP_MAX_SIZE;
b->msgvec[n].msg_hdr = (struct msghdr) {
.msg_name = &b->addr,
.msg_namelen = sizeof (struct sockaddr_storage),
.msg_iov = &b->iov[n],
.msg_iovlen = 1,
.msg_control = b->control[n],
.msg_controllen = 256,
};
}
return (struct us_udp_packet_buffer_t *) b;
#endif
}
LIBUS_SOCKET_DESCRIPTOR apple_no_sigpipe(LIBUS_SOCKET_DESCRIPTOR fd) {
#ifdef __APPLE__
if (fd != LIBUS_SOCKET_ERROR) {
int no_sigpipe = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, (void *) &no_sigpipe, sizeof(int));
}
#endif
return fd;
}
LIBUS_SOCKET_DESCRIPTOR bsd_set_nonblocking(LIBUS_SOCKET_DESCRIPTOR fd) {
#ifdef _WIN32
/* Libuv will set windows sockets as non-blocking */
#else
fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK);
#endif
return fd;
}
void bsd_socket_nodelay(LIBUS_SOCKET_DESCRIPTOR fd, int enabled) {
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *) &enabled, sizeof(enabled));
}
void bsd_socket_flush(LIBUS_SOCKET_DESCRIPTOR fd) {
// Linux TCP_CORK has the same underlying corking mechanism as with MSG_MORE
#ifdef TCP_CORK
int enabled = 0;
setsockopt(fd, IPPROTO_TCP, TCP_CORK, (void *) &enabled, sizeof(int));
#endif
}
LIBUS_SOCKET_DESCRIPTOR bsd_create_socket(int domain, int type, int protocol) {
// returns INVALID_SOCKET on error
int flags = 0;
#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
flags = SOCK_CLOEXEC | SOCK_NONBLOCK;
#endif
LIBUS_SOCKET_DESCRIPTOR created_fd = socket(domain, type | flags, protocol);
return bsd_set_nonblocking(apple_no_sigpipe(created_fd));
}
void bsd_close_socket(LIBUS_SOCKET_DESCRIPTOR fd) {
#ifdef _WIN32
closesocket(fd);
#else
close(fd);
#endif
}
void bsd_shutdown_socket(LIBUS_SOCKET_DESCRIPTOR fd) {
#ifdef _WIN32
shutdown(fd, SD_SEND);
#else
shutdown(fd, SHUT_WR);
#endif
}
void bsd_shutdown_socket_read(LIBUS_SOCKET_DESCRIPTOR fd) {
#ifdef _WIN32
shutdown(fd, SD_RECEIVE);
#else
shutdown(fd, SHUT_RD);
#endif
}
void internal_finalize_bsd_addr(struct bsd_addr_t *addr) {
// parse, so to speak, the address
if (addr->mem.ss_family == AF_INET6) {
addr->ip = (char *) &((struct sockaddr_in6 *) addr)->sin6_addr;
addr->ip_length = sizeof(struct in6_addr);
addr->port = ntohs(((struct sockaddr_in6 *) addr)->sin6_port);
} else if (addr->mem.ss_family == AF_INET) {
addr->ip = (char *) &((struct sockaddr_in *) addr)->sin_addr;
addr->ip_length = sizeof(struct in_addr);
addr->port = ntohs(((struct sockaddr_in *) addr)->sin_port);
} else {
addr->ip_length = 0;
addr->port = -1;
}
}
int bsd_local_addr(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd_addr_t *addr) {
addr->len = sizeof(addr->mem);
if (getsockname(fd, (struct sockaddr *) &addr->mem, &addr->len)) {
return -1;
}
internal_finalize_bsd_addr(addr);
return 0;
}
int bsd_remote_addr(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd_addr_t *addr) {
addr->len = sizeof(addr->mem);
if (getpeername(fd, (struct sockaddr *) &addr->mem, &addr->len)) {
return -1;
}
internal_finalize_bsd_addr(addr);
return 0;
}
char *bsd_addr_get_ip(struct bsd_addr_t *addr) {
return addr->ip;
}
int bsd_addr_get_ip_length(struct bsd_addr_t *addr) {
return addr->ip_length;
}
int bsd_addr_get_port(struct bsd_addr_t *addr) {
return addr->port;
}
// called by dispatch_ready_poll
LIBUS_SOCKET_DESCRIPTOR bsd_accept_socket(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd_addr_t *addr) {
LIBUS_SOCKET_DESCRIPTOR accepted_fd;
addr->len = sizeof(addr->mem);
#if defined(SOCK_CLOEXEC) && defined(SOCK_NONBLOCK)
// Linux, FreeBSD
accepted_fd = accept4(fd, (struct sockaddr *) addr, &addr->len, SOCK_CLOEXEC | SOCK_NONBLOCK);
#else
// Windows, OS X
accepted_fd = accept(fd, (struct sockaddr *) addr, &addr->len);
#endif
/* We cannot rely on addr since it is not initialized if failed */
if (accepted_fd == LIBUS_SOCKET_ERROR) {
return LIBUS_SOCKET_ERROR;
}
internal_finalize_bsd_addr(addr);
return bsd_set_nonblocking(apple_no_sigpipe(accepted_fd));
}
int bsd_recv(LIBUS_SOCKET_DESCRIPTOR fd, void *buf, int length, int flags) {
return recv(fd, buf, length, flags);
}
int bsd_send(LIBUS_SOCKET_DESCRIPTOR fd, const char *buf, int length, int msg_more) {
// MSG_MORE (Linux), MSG_PARTIAL (Windows), TCP_NOPUSH (BSD)
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
#ifdef MSG_MORE
// for Linux we do not want signals
return send(fd, buf, length, ((msg_more != 0) * MSG_MORE) | MSG_NOSIGNAL);
#else
// use TCP_NOPUSH
return send(fd, buf, length, MSG_NOSIGNAL);
#endif
}
int bsd_would_block() {
#ifdef _WIN32
return WSAGetLastError() == WSAEWOULDBLOCK;
#else
return errno == EWOULDBLOCK;// || errno == EAGAIN;
#endif
}
// return LIBUS_SOCKET_ERROR or the fd that represents listen socket
// listen both on ipv6 and ipv4
LIBUS_SOCKET_DESCRIPTOR bsd_create_listen_socket(const char *host, int port, int options) {
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
char port_string[16];
snprintf(port_string, 16, "%d", port);
if (getaddrinfo(host, port_string, &hints, &result)) {
return LIBUS_SOCKET_ERROR;
}
LIBUS_SOCKET_DESCRIPTOR listenFd = LIBUS_SOCKET_ERROR;
struct addrinfo *listenAddr;
for (struct addrinfo *a = result; a && listenFd == LIBUS_SOCKET_ERROR; a = a->ai_next) {
if (a->ai_family == AF_INET6) {
listenFd = bsd_create_socket(a->ai_family, a->ai_socktype, a->ai_protocol);
listenAddr = a;
}
}
for (struct addrinfo *a = result; a && listenFd == LIBUS_SOCKET_ERROR; a = a->ai_next) {
if (a->ai_family == AF_INET) {
listenFd = bsd_create_socket(a->ai_family, a->ai_socktype, a->ai_protocol);
listenAddr = a;
}
}
if (listenFd == LIBUS_SOCKET_ERROR) {
freeaddrinfo(result);
return LIBUS_SOCKET_ERROR;
}
if (port != 0) {
/* Otherwise, always enable SO_REUSEPORT and SO_REUSEADDR _unless_ options specify otherwise */
#ifdef _WIN32
if (options & LIBUS_LISTEN_EXCLUSIVE_PORT) {
int optval2 = 1;
setsockopt(listenFd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (void *) &optval2, sizeof(optval2));
} else {
int optval3 = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (void *) &optval3, sizeof(optval3));
}
#else
#if /*defined(__linux) &&*/ defined(SO_REUSEPORT)
if (!(options & LIBUS_LISTEN_EXCLUSIVE_PORT)) {
int optval = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, (void *) &optval, sizeof(optval));
}
#endif
int enabled = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (void *) &enabled, sizeof(enabled));
#endif
}
#ifdef IPV6_V6ONLY
int disabled = 0;
setsockopt(listenFd, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &disabled, sizeof(disabled));
#endif
if (bind(listenFd, listenAddr->ai_addr, (socklen_t) listenAddr->ai_addrlen) || listen(listenFd, 512)) {
bsd_close_socket(listenFd);
freeaddrinfo(result);
return LIBUS_SOCKET_ERROR;
}
freeaddrinfo(result);
return listenFd;
}
#ifndef _WIN32
#include <sys/un.h>
#else
#include <afunix.h>
#include <io.h>
#endif
#include <sys/stat.h>
#include <stddef.h>
LIBUS_SOCKET_DESCRIPTOR bsd_create_listen_socket_unix(const char *path, int options) {
LIBUS_SOCKET_DESCRIPTOR listenFd = LIBUS_SOCKET_ERROR;
listenFd = bsd_create_socket(AF_UNIX, SOCK_STREAM, 0);
if (listenFd == LIBUS_SOCKET_ERROR) {
return LIBUS_SOCKET_ERROR;
}
#ifndef _WIN32
// 700 permission by default
fchmod(listenFd, S_IRWXU);
#else
_chmod(path, S_IREAD | S_IWRITE | S_IEXEC);
#endif
struct sockaddr_un server_address;
memset(&server_address, 0, sizeof(server_address));
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, path);
int size = offsetof(struct sockaddr_un, sun_path) + strlen(server_address.sun_path);
#ifdef _WIN32
_unlink(path);
#else
unlink(path);
#endif
if (bind(listenFd, (struct sockaddr *)&server_address, size) || listen(listenFd, 512)) {
bsd_close_socket(listenFd);
return LIBUS_SOCKET_ERROR;
}
return listenFd;
}
LIBUS_SOCKET_DESCRIPTOR bsd_create_udp_socket(const char *host, int port) {
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
char port_string[16];
snprintf(port_string, 16, "%d", port);
if (getaddrinfo(host, port_string, &hints, &result)) {
return LIBUS_SOCKET_ERROR;
}
LIBUS_SOCKET_DESCRIPTOR listenFd = LIBUS_SOCKET_ERROR;
struct addrinfo *listenAddr;
for (struct addrinfo *a = result; a && listenFd == LIBUS_SOCKET_ERROR; a = a->ai_next) {
if (a->ai_family == AF_INET6) {
listenFd = bsd_create_socket(a->ai_family, a->ai_socktype, a->ai_protocol);
listenAddr = a;
}
}
for (struct addrinfo *a = result; a && listenFd == LIBUS_SOCKET_ERROR; a = a->ai_next) {
if (a->ai_family == AF_INET) {
listenFd = bsd_create_socket(a->ai_family, a->ai_socktype, a->ai_protocol);
listenAddr = a;
}
}
if (listenFd == LIBUS_SOCKET_ERROR) {
freeaddrinfo(result);
return LIBUS_SOCKET_ERROR;
}
if (port != 0) {
/* Should this also go for UDP? */
int enabled = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (void *) &enabled, sizeof(enabled));
}
#ifdef IPV6_V6ONLY
int disabled = 0;
setsockopt(listenFd, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &disabled, sizeof(disabled));
#endif
/* We need destination address for udp packets in both ipv6 and ipv4 */
/* On FreeBSD this option seems to be called like so */
#ifndef IPV6_RECVPKTINFO
#define IPV6_RECVPKTINFO IPV6_PKTINFO
#endif
int enabled = 1;
if (setsockopt(listenFd, IPPROTO_IPV6, IPV6_RECVPKTINFO, (void *) &enabled, sizeof(enabled)) == -1) {
if (errno == 92) {
if (setsockopt(listenFd, IPPROTO_IP, IP_PKTINFO, (void *) &enabled, sizeof(enabled)) != 0) {
printf("Error setting IPv4 pktinfo!\n");
}
} else {
printf("Error setting IPv6 pktinfo!\n");
}
}
/* These are used for getting the ECN */
if (setsockopt(listenFd, IPPROTO_IPV6, IPV6_RECVTCLASS, (void *) &enabled, sizeof(enabled)) == -1) {
if (errno == 92) {
if (setsockopt(listenFd, IPPROTO_IP, IP_RECVTOS, (void *) &enabled, sizeof(enabled)) != 0) {
printf("Error setting IPv4 ECN!\n");
}
} else {
printf("Error setting IPv6 ECN!\n");
}
}
/* We bind here as well */
if (bind(listenFd, listenAddr->ai_addr, (socklen_t) listenAddr->ai_addrlen)) {
bsd_close_socket(listenFd);
freeaddrinfo(result);
return LIBUS_SOCKET_ERROR;
}
freeaddrinfo(result);
return listenFd;
}
int bsd_udp_packet_buffer_ecn(void *msgvec, int index) {
#if defined(_WIN32) || defined(__APPLE__)
printf("ECN not supported!\n");
#else
// we should iterate all control messages once, after recvmmsg and then only fetch them with these functions
struct msghdr *mh = &((struct mmsghdr *) msgvec)[index].msg_hdr;
for (struct cmsghdr *cmsg = CMSG_FIRSTHDR(mh); cmsg != NULL; cmsg = CMSG_NXTHDR(mh, cmsg)) {
// do we need to get TOS from ipv6 also?
if (cmsg->cmsg_level == IPPROTO_IP) {
if (cmsg->cmsg_type == IP_TOS) {
uint8_t tos = *(uint8_t *)CMSG_DATA(cmsg);
return tos & 3;
}
}
if (cmsg->cmsg_level == IPPROTO_IPV6) {
if (cmsg->cmsg_type == IPV6_TCLASS) {
// is this correct?
uint8_t tos = *(uint8_t *)CMSG_DATA(cmsg);
return tos & 3;
}
}
}
#endif
printf("We got no ECN!\n");
return 0; // no ecn defaults to 0
}
static int bsd_do_connect(struct addrinfo *result, int fd)
{
return connect(fd, result->ai_addr, (socklen_t) result->ai_addrlen);
}
LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket(const char *host, int port, const char *source_host, int options) {
struct addrinfo hints, *result;
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
char port_string[16];
snprintf(port_string, 16, "%d", port);
if (getaddrinfo(host, port_string, &hints, &result) != 0) {
return LIBUS_SOCKET_ERROR;
}
LIBUS_SOCKET_DESCRIPTOR fd = bsd_create_socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (fd == LIBUS_SOCKET_ERROR) {
freeaddrinfo(result);
return LIBUS_SOCKET_ERROR;
}
if (source_host) {
struct addrinfo *interface_result;
if (!getaddrinfo(source_host, NULL, NULL, &interface_result)) {
int ret = bind(fd, interface_result->ai_addr, (socklen_t) interface_result->ai_addrlen);
freeaddrinfo(interface_result);
if (ret == LIBUS_SOCKET_ERROR) {
bsd_close_socket(fd);
freeaddrinfo(result);
return LIBUS_SOCKET_ERROR;
}
}
}
do {
if (bsd_do_connect(result, fd) != 0 && errno != EINPROGRESS) {
bsd_close_socket(fd);
freeaddrinfo(result);
return LIBUS_SOCKET_ERROR;
}
} while (errno == EINTR);
freeaddrinfo(result);
return fd;
}
LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket_unix(const char *server_path, int options) {
struct sockaddr_un server_address;
memset(&server_address, 0, sizeof(server_address));
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, server_path);
int size = offsetof(struct sockaddr_un, sun_path) + strlen(server_address.sun_path);
LIBUS_SOCKET_DESCRIPTOR fd = bsd_create_socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == LIBUS_SOCKET_ERROR) {
return LIBUS_SOCKET_ERROR;
}
if (connect(fd, (struct sockaddr *)&server_address, size) != 0 && errno != EINPROGRESS) {
bsd_close_socket(fd);
return LIBUS_SOCKET_ERROR;
}
return fd;
}

View File

@@ -0,0 +1,575 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "libusockets.h"
#include "internal/internal.h"
#include <stdlib.h>
#include <string.h>
int default_is_low_prio_handler(struct us_socket_t *s) {
return 0;
}
/* Shared with SSL */
unsigned short us_socket_context_timestamp(int ssl, struct us_socket_context_t *context) {
return context->timestamp;
}
int us_raw_root_certs(struct us_cert_string_t**out){
return us_internal_raw_root_certs(out);
}
void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls) {
/* us_listen_socket_t extends us_socket_t so we close in similar ways */
if (!us_socket_is_closed(0, &ls->s)) {
us_internal_socket_context_unlink_listen_socket(ls->s.context, ls);
us_poll_stop((struct us_poll_t *) &ls->s, ls->s.context->loop);
bsd_close_socket(us_poll_fd((struct us_poll_t *) &ls->s));
/* Link this socket to the close-list and let it be deleted after this iteration */
ls->s.next = ls->s.context->loop->data.closed_head;
ls->s.context->loop->data.closed_head = &ls->s;
/* Any socket with prev = context is marked as closed */
ls->s.prev = (struct us_socket_t *) ls->s.context;
}
/* We cannot immediately free a listen socket as we can be inside an accept loop */
}
void us_socket_context_close(int ssl, struct us_socket_context_t *context) {
/* Begin by closing all listen sockets */
struct us_listen_socket_t *ls = context->head_listen_sockets;
while (ls) {
struct us_listen_socket_t *nextLS = (struct us_listen_socket_t *) ls->s.next;
us_listen_socket_close(ssl, ls);
ls = nextLS;
}
/* Then close all regular sockets */
struct us_socket_t *s = context->head_sockets;
while (s) {
struct us_socket_t *nextS = s->next;
us_socket_close(ssl, s, 0, 0);
s = nextS;
}
}
void us_internal_socket_context_unlink_listen_socket(struct us_socket_context_t *context, struct us_listen_socket_t *ls) {
/* We have to properly update the iterator used to sweep sockets for timeouts */
if (ls == (struct us_listen_socket_t *) context->iterator) {
context->iterator = ls->s.next;
}
if (ls->s.prev == ls->s.next) {
context->head_listen_sockets = 0;
} else {
if (ls->s.prev) {
ls->s.prev->next = ls->s.next;
} else {
context->head_listen_sockets = (struct us_listen_socket_t *) ls->s.next;
}
if (ls->s.next) {
ls->s.next->prev = ls->s.prev;
}
}
}
void us_internal_socket_context_unlink_socket(struct us_socket_context_t *context, struct us_socket_t *s) {
/* We have to properly update the iterator used to sweep sockets for timeouts */
if (s == context->iterator) {
context->iterator = s->next;
}
if (s->prev == s->next) {
context->head_sockets = 0;
} else {
if (s->prev) {
s->prev->next = s->next;
} else {
context->head_sockets = s->next;
}
if (s->next) {
s->next->prev = s->prev;
}
}
}
/* We always add in the top, so we don't modify any s.next */
void us_internal_socket_context_link_listen_socket(struct us_socket_context_t *context, struct us_listen_socket_t *ls) {
ls->s.context = context;
ls->s.next = (struct us_socket_t *) context->head_listen_sockets;
ls->s.prev = 0;
if (context->head_listen_sockets) {
context->head_listen_sockets->s.prev = &ls->s;
}
context->head_listen_sockets = ls;
}
/* We always add in the top, so we don't modify any s.next */
void us_internal_socket_context_link_socket(struct us_socket_context_t *context, struct us_socket_t *s) {
s->context = context;
s->next = context->head_sockets;
s->prev = 0;
if (context->head_sockets) {
context->head_sockets->prev = s;
}
context->head_sockets = s;
}
struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context) {
return context->loop;
}
/* Not shared with SSL */
/* Lookup userdata by server name pattern */
void *us_socket_context_find_server_name_userdata(int ssl, struct us_socket_context_t *context, const char *hostname_pattern) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_context_find_server_name_userdata((struct us_internal_ssl_socket_context_t *) context, hostname_pattern);
}
#endif
return NULL;
}
/* Get userdata attached to this SNI-routed socket, or nullptr if default */
void *us_socket_server_name_userdata(int ssl, struct us_socket_t *s) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_get_sni_userdata((struct us_internal_ssl_socket_t *) s);
}
#endif
return NULL;
}
/* Add SNI context */
void us_socket_context_add_server_name(int ssl, struct us_socket_context_t *context, const char *hostname_pattern, struct us_socket_context_options_t options, void *user) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_add_server_name((struct us_internal_ssl_socket_context_t *) context, hostname_pattern, options, user);
}
#endif
}
void us_bun_socket_context_add_server_name(int ssl, struct us_socket_context_t *context, const char *hostname_pattern, struct us_bun_socket_context_options_t options, void *user) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_bun_internal_ssl_socket_context_add_server_name((struct us_internal_ssl_socket_context_t *) context, hostname_pattern, options, user);
}
#endif
}
/* Remove SNI context */
void us_socket_context_remove_server_name(int ssl, struct us_socket_context_t *context, const char *hostname_pattern) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_remove_server_name((struct us_internal_ssl_socket_context_t *) context, hostname_pattern);
}
#endif
}
/* I don't like this one - maybe rename it to on_missing_server_name? */
/* Called when SNI matching fails - not if a match could be made.
* You may modify the context by adding/removing names in this callback.
* If the correct name is added immediately in the callback, it will be used */
void us_socket_context_on_server_name(int ssl, struct us_socket_context_t *context, void (*cb)(struct us_socket_context_t *, const char *hostname)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_server_name((struct us_internal_ssl_socket_context_t *) context, (void (*)(struct us_internal_ssl_socket_context_t *, const char *hostname)) cb);
}
#endif
}
/* Todo: get native context from SNI pattern */
void *us_socket_context_get_native_handle(int ssl, struct us_socket_context_t *context) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_context_get_native_handle((struct us_internal_ssl_socket_context_t *) context);
}
#endif
/* There is no native handle for a non-SSL socket context */
return 0;
}
/* Options is currently only applicable for SSL - this will change with time (prefer_low_memory is one example) */
struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *loop, int context_ext_size, struct us_socket_context_options_t options) {
#ifndef LIBUS_NO_SSL
if (ssl) {
/* This function will call us, again, with SSL = false and a bigger ext_size */
return (struct us_socket_context_t *) us_internal_create_ssl_socket_context(loop, context_ext_size, options);
}
#endif
/* This path is taken once either way - always BEFORE whatever SSL may do LATER.
* context_ext_size will however be modified larger in case of SSL, to hold SSL extensions */
struct us_socket_context_t *context = us_malloc(sizeof(struct us_socket_context_t) + context_ext_size);
context->loop = loop;
context->head_sockets = 0;
context->head_listen_sockets = 0;
context->iterator = 0;
context->next = 0;
context->is_low_prio = default_is_low_prio_handler;
/* Begin at 0 */
context->timestamp = 0;
context->long_timestamp = 0;
context->global_tick = 0;
us_internal_loop_link(loop, context);
/* If we are called from within SSL code, SSL code will make further changes to us */
return context;
}
struct us_socket_context_t *us_create_bun_socket_context(int ssl, struct us_loop_t *loop, int context_ext_size, struct us_bun_socket_context_options_t options) {
#ifndef LIBUS_NO_SSL
if (ssl) {
/* This function will call us, again, with SSL = false and a bigger ext_size */
return (struct us_socket_context_t *) us_internal_bun_create_ssl_socket_context(loop, context_ext_size, options);
}
#endif
/* This path is taken once either way - always BEFORE whatever SSL may do LATER.
* context_ext_size will however be modified larger in case of SSL, to hold SSL extensions */
struct us_socket_context_t *context = us_malloc(sizeof(struct us_socket_context_t) + context_ext_size);
context->loop = loop;
context->head_sockets = 0;
context->head_listen_sockets = 0;
context->iterator = 0;
context->next = 0;
context->is_low_prio = default_is_low_prio_handler;
/* Begin at 0 */
context->timestamp = 0;
context->long_timestamp = 0;
context->global_tick = 0;
us_internal_loop_link(loop, context);
/* If we are called from within SSL code, SSL code will make further changes to us */
return context;
}
struct us_bun_verify_error_t us_socket_verify_error(int ssl, struct us_socket_t *socket) {
#ifndef LIBUS_NO_SSL
if (ssl) {
/* This function will call us again with SSL=false */
return us_internal_verify_error((struct us_internal_ssl_socket_t *)socket);
}
#endif
return (struct us_bun_verify_error_t) { .error = 0, .code = NULL, .reason = NULL };
}
void us_socket_context_free(int ssl, struct us_socket_context_t *context) {
#ifndef LIBUS_NO_SSL
if (ssl) {
/* This function will call us again with SSL=false */
us_internal_ssl_socket_context_free((struct us_internal_ssl_socket_context_t *) context);
return;
}
#endif
/* This path is taken once either way - always AFTER whatever SSL may do BEFORE.
* This is the opposite order compared to when creating the context - SSL code is cleaning up before non-SSL */
us_internal_loop_unlink(context->loop, context);
us_free(context);
}
struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_context_t *context, const char *host, int port, int options, int socket_ext_size) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_context_listen((struct us_internal_ssl_socket_context_t *) context, host, port, options, socket_ext_size);
}
#endif
LIBUS_SOCKET_DESCRIPTOR listen_socket_fd = bsd_create_listen_socket(host, port, options);
if (listen_socket_fd == LIBUS_SOCKET_ERROR) {
return 0;
}
struct us_poll_t *p = us_create_poll(context->loop, 0, sizeof(struct us_listen_socket_t));
us_poll_init(p, listen_socket_fd, POLL_TYPE_SEMI_SOCKET);
us_poll_start(p, context->loop, LIBUS_SOCKET_READABLE);
struct us_listen_socket_t *ls = (struct us_listen_socket_t *) p;
ls->s.context = context;
ls->s.timeout = 255;
ls->s.long_timeout = 255;
ls->s.low_prio_state = 0;
ls->s.next = 0;
us_internal_socket_context_link_listen_socket(context, ls);
ls->socket_ext_size = socket_ext_size;
return ls;
}
struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_socket_context_t *context, const char *path, int options, int socket_ext_size) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_context_listen_unix((struct us_internal_ssl_socket_context_t *) context, path, options, socket_ext_size);
}
#endif
LIBUS_SOCKET_DESCRIPTOR listen_socket_fd = bsd_create_listen_socket_unix(path, options);
if (listen_socket_fd == LIBUS_SOCKET_ERROR) {
return 0;
}
struct us_poll_t *p = us_create_poll(context->loop, 0, sizeof(struct us_listen_socket_t));
us_poll_init(p, listen_socket_fd, POLL_TYPE_SEMI_SOCKET);
us_poll_start(p, context->loop, LIBUS_SOCKET_READABLE);
struct us_listen_socket_t *ls = (struct us_listen_socket_t *) p;
ls->s.context = context;
ls->s.timeout = 255;
ls->s.long_timeout = 255;
ls->s.low_prio_state = 0;
ls->s.next = 0;
us_internal_socket_context_link_listen_socket(context, ls);
ls->socket_ext_size = socket_ext_size;
return ls;
}
struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context, const char *host, int port, const char *source_host, int options, int socket_ext_size) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return (struct us_socket_t *) us_internal_ssl_socket_context_connect((struct us_internal_ssl_socket_context_t *) context, host, port, source_host, options, socket_ext_size);
}
#endif
LIBUS_SOCKET_DESCRIPTOR connect_socket_fd = bsd_create_connect_socket(host, port, source_host, options);
if (connect_socket_fd == LIBUS_SOCKET_ERROR) {
return 0;
}
/* Connect sockets are semi-sockets just like listen sockets */
struct us_poll_t *p = us_create_poll(context->loop, 0, sizeof(struct us_socket_t) + socket_ext_size);
us_poll_init(p, connect_socket_fd, POLL_TYPE_SEMI_SOCKET);
us_poll_start(p, context->loop, LIBUS_SOCKET_WRITABLE);
struct us_socket_t *connect_socket = (struct us_socket_t *) p;
/* Link it into context so that timeout fires properly */
connect_socket->context = context;
connect_socket->timeout = 255;
connect_socket->long_timeout = 255;
connect_socket->low_prio_state = 0;
us_internal_socket_context_link_socket(context, connect_socket);
return connect_socket;
}
struct us_socket_t *us_socket_context_connect_unix(int ssl, struct us_socket_context_t *context, const char *server_path, int options, int socket_ext_size) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return (struct us_socket_t *) us_internal_ssl_socket_context_connect_unix((struct us_internal_ssl_socket_context_t *) context, server_path, options, socket_ext_size);
}
#endif
LIBUS_SOCKET_DESCRIPTOR connect_socket_fd = bsd_create_connect_socket_unix(server_path, options);
if (connect_socket_fd == LIBUS_SOCKET_ERROR) {
return 0;
}
/* Connect sockets are semi-sockets just like listen sockets */
struct us_poll_t *p = us_create_poll(context->loop, 0, sizeof(struct us_socket_t) + socket_ext_size);
us_poll_init(p, connect_socket_fd, POLL_TYPE_SEMI_SOCKET);
us_poll_start(p, context->loop, LIBUS_SOCKET_WRITABLE);
struct us_socket_t *connect_socket = (struct us_socket_t *) p;
/* Link it into context so that timeout fires properly */
connect_socket->context = context;
connect_socket->timeout = 255;
connect_socket->long_timeout = 255;
connect_socket->low_prio_state = 0;
us_internal_socket_context_link_socket(context, connect_socket);
return connect_socket;
}
struct us_socket_context_t *us_create_child_socket_context(int ssl, struct us_socket_context_t *context, int context_ext_size) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return (struct us_socket_context_t *) us_internal_create_child_ssl_socket_context((struct us_internal_ssl_socket_context_t *) context, context_ext_size);
}
#endif
/* For TCP we simply create a new context as nothing is shared */
struct us_socket_context_options_t options = {0};
return us_create_socket_context(ssl, context->loop, context_ext_size, options);
}
/* Note: This will set timeout to 0 */
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int ext_size) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return (struct us_socket_t *) us_internal_ssl_socket_context_adopt_socket((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t *) s, ext_size);
}
#endif
/* Cannot adopt a closed socket */
if (us_socket_is_closed(ssl, s)) {
return s;
}
if (s->low_prio_state != 1) {
/* This properly updates the iterator if in on_timeout */
us_internal_socket_context_unlink_socket(s->context, s);
}
struct us_socket_t *new_s = (struct us_socket_t *) us_poll_resize(&s->p, s->context->loop, sizeof(struct us_socket_t) + ext_size);
new_s->timeout = 255;
new_s->long_timeout = 255;
if (new_s->low_prio_state == 1) {
/* update pointers in low-priority queue */
if (!new_s->prev) new_s->context->loop->data.low_prio_head = new_s;
else new_s->prev->next = new_s;
if (new_s->next) new_s->next->prev = new_s;
} else {
us_internal_socket_context_link_socket(context, new_s);
}
return new_s;
}
void us_socket_context_on_open(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_open((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t * (*)(struct us_internal_ssl_socket_t *, int, char *, int)) on_open);
return;
}
#endif
context->on_open = on_open;
}
void us_socket_context_on_close(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_close)(struct us_socket_t *s, int code, void *reason)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_close((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t * (*)(struct us_internal_ssl_socket_t *, int code, void *reason)) on_close);
return;
}
#endif
context->on_close = on_close;
}
void us_socket_context_on_data(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_data((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t * (*)(struct us_internal_ssl_socket_t *, char *, int)) on_data);
return;
}
#endif
context->on_data = on_data;
}
void us_socket_context_on_writable(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_writable)(struct us_socket_t *s)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_writable((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t * (*)(struct us_internal_ssl_socket_t *)) on_writable);
return;
}
#endif
context->on_writable = on_writable;
}
void us_socket_context_on_long_timeout(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_long_timeout)(struct us_socket_t *)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_long_timeout((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t * (*)(struct us_internal_ssl_socket_t *)) on_long_timeout);
return;
}
#endif
context->on_socket_long_timeout = on_long_timeout;
}
void us_socket_context_on_timeout(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_timeout)(struct us_socket_t *)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_timeout((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t * (*)(struct us_internal_ssl_socket_t *)) on_timeout);
return;
}
#endif
context->on_socket_timeout = on_timeout;
}
void us_socket_context_on_end(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_end)(struct us_socket_t *)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_end((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t * (*)(struct us_internal_ssl_socket_t *)) on_end);
return;
}
#endif
context->on_end = on_end;
}
void us_socket_context_on_connect_error(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_connect_error)(struct us_socket_t *s, int code)) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_context_on_connect_error((struct us_internal_ssl_socket_context_t *) context, (struct us_internal_ssl_socket_t * (*)(struct us_internal_ssl_socket_t *, int)) on_connect_error);
return;
}
#endif
context->on_connect_error = on_connect_error;
}
void *us_socket_context_ext(int ssl, struct us_socket_context_t *context) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_context_ext((struct us_internal_ssl_socket_context_t *) context);
}
#endif
return context + 1;
}
void us_socket_context_on_handshake(int ssl, struct us_socket_context_t *context, void (*on_handshake)(struct us_socket_context_t *, int success, struct us_bun_verify_error_t verify_error, void* custom_data), void* custom_data) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_on_ssl_handshake((struct us_internal_ssl_socket_context_t *) context, on_handshake, custom_data);
return;
}
#endif
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,218 @@
/*
* Authored by Alex Hultman, 2018-2020.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* This Server Name Indication hostname tree is written in C++ but could be ported to C.
* Overall it looks like crap, but has no memory allocations in fast path and is O(log n). */
#ifndef SNI_TREE_H
#define SNI_TREE_H
#ifndef LIBUS_NO_SSL
#include <map>
#include <memory>
#include <string_view>
#include <cstring>
#include <cstdlib>
#include <algorithm>
/* We only handle a maximum of 10 labels per hostname */
#define MAX_LABELS 10
/* This cannot be shared */
thread_local void (*sni_free_cb)(void *);
struct sni_node {
/* Empty nodes must always hold null */
void *user = nullptr;
std::map<std::string_view, std::unique_ptr<sni_node>> children;
~sni_node() {
for (auto &p : children) {
/* The data of our string_views are managed by malloc */
free((void *) p.first.data());
/* Call destructor passed to sni_free only if we hold data.
* This is important since sni_remove does not have sni_free_cb set */
if (p.second.get()->user) {
sni_free_cb(p.second.get()->user);
}
}
}
};
// this can only delete ONE single node, but may cull "empty nodes with null as data"
void *removeUser(struct sni_node *root, unsigned int label, std::string_view *labels, unsigned int numLabels) {
/* If we are in the bottom (past bottom by one), there is nothing to remove */
if (label == numLabels) {
void *user = root->user;
/* Mark us for culling on the way up */
root->user = nullptr;
return user;
}
/* Is this label a child of root? */
auto it = root->children.find(labels[label]);
if (it == root->children.end()) {
/* We cannot continue */
return nullptr;
}
void *removedUser = removeUser(it->second.get(), label + 1, labels, numLabels);
/* On the way back up, we may cull empty nodes with no children.
* This ends up being where we remove all nodes */
if (it->second.get()->children.empty() && it->second.get()->user == nullptr) {
/* The data of our string_views are managed by malloc */
free((void *) it->first.data());
/* This can only happen with user set to null, otherwise we use sni_free_cb which is unset by sni_remove */
root->children.erase(it);
}
return removedUser;
}
void *getUser(struct sni_node *root, unsigned int label, std::string_view *labels, unsigned int numLabels) {
/* Do we have labels to match? Otherwise, return where we stand */
if (label == numLabels) {
return root->user;
}
/* Try and match by our label */
auto it = root->children.find(labels[label]);
if (it != root->children.end()) {
void *user = getUser(it->second.get(), label + 1, labels, numLabels);
if (user) {
return user;
}
}
/* Try and match by wildcard */
it = root->children.find("*");
if (it == root->children.end()) {
/* Matching has failed for both label and wildcard */
return nullptr;
}
/* We matched by wildcard */
return getUser(it->second.get(), label + 1, labels, numLabels);
}
extern "C" {
void *sni_new() {
return new sni_node;
}
void sni_free(void *sni, void (*cb)(void *)) {
/* We want to run this callback for every remaining name */
sni_free_cb = cb;
delete (sni_node *) sni;
}
/* Returns non-null if this name already exists */
int sni_add(void *sni, const char *hostname, void *user) {
struct sni_node *root = (struct sni_node *) sni;
/* Traverse all labels in hostname */
for (std::string_view view(hostname, strlen(hostname)), label;
view.length(); view.remove_prefix(std::min(view.length(), label.length() + 1))) {
/* Label is the token separated by dot */
label = view.substr(0, view.find('.', 0));
auto it = root->children.find(label);
if (it == root->children.end()) {
/* Duplicate this label for our kept string_view of it */
void *labelString = malloc(label.length());
memcpy(labelString, label.data(), label.length());
it = root->children.emplace(std::string_view((char *) labelString, label.length()),
std::make_unique<sni_node>()).first;
}
root = it->second.get();
}
/* We must never add multiple contexts for the same name, as that would overwrite and leak */
if (root->user) {
return 1;
}
root->user = user;
return 0;
}
/* Removes the exact match. Wildcards are treated as the verbatim asterisk char, not as an actual wildcard */
void *sni_remove(void *sni, const char *hostname) {
struct sni_node *root = (struct sni_node *) sni;
/* I guess 10 labels is an okay limit */
std::string_view labels[10];
unsigned int numLabels = 0;
/* We traverse all labels first of all */
for (std::string_view view(hostname, strlen(hostname)), label;
view.length(); view.remove_prefix(std::min(view.length(), label.length() + 1))) {
/* Label is the token separated by dot */
label = view.substr(0, view.find('.', 0));
/* Anything longer than 10 labels is forbidden */
if (numLabels == 10) {
return nullptr;
}
labels[numLabels++] = label;
}
return removeUser(root, 0, labels, numLabels);
}
void *sni_find(void *sni, const char *hostname) {
struct sni_node *root = (struct sni_node *) sni;
/* I guess 10 labels is an okay limit */
std::string_view labels[10];
unsigned int numLabels = 0;
/* We traverse all labels first of all */
for (std::string_view view(hostname, strlen(hostname)), label;
view.length(); view.remove_prefix(std::min(view.length(), label.length() + 1))) {
/* Label is the token separated by dot */
label = view.substr(0, view.find('.', 0));
/* Anything longer than 10 labels is forbidden */
if (numLabels == 10) {
return nullptr;
}
labels[numLabels++] = label;
}
return getUser(root, 0, labels, numLabels);
}
}
#endif
#endif

View File

@@ -0,0 +1,581 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "libusockets.h"
#include "internal/internal.h"
#include <stdlib.h>
#if defined(LIBUS_USE_EPOLL) || defined(LIBUS_USE_KQUEUE)
void Bun__internal_dispatch_ready_poll(void* loop, void* poll);
// void Bun__internal_dispatch_ready_poll(void* loop, void* poll) {}
#ifndef WIN32
/* Cannot include this one on Windows */
#include <unistd.h>
#include <stdint.h>
#endif
void us_loop_run_bun_tick(struct us_loop_t *loop, int64_t timeoutMs);
/* Pointer tags are used to indicate a Bun pointer versus a uSockets pointer */
#define UNSET_BITS_49_UNTIL_64 0x0000FFFFFFFFFFFF
#define CLEAR_POINTER_TAG(p) ((void *) ((uintptr_t) (p) & UNSET_BITS_49_UNTIL_64))
#define LIKELY(cond) __builtin_expect((uint64_t)(void*)cond, 1)
#define UNLIKELY(cond) __builtin_expect((uint64_t)(void*)cond, 0)
#ifdef LIBUS_USE_EPOLL
#define GET_READY_POLL(loop, index) (struct us_poll_t *) loop->ready_polls[index].data.ptr
#define SET_READY_POLL(loop, index, poll) loop->ready_polls[index].data.ptr = (void*)poll
#else
#define GET_READY_POLL(loop, index) (struct us_poll_t *) loop->ready_polls[index].udata
#define SET_READY_POLL(loop, index, poll) loop->ready_polls[index].udata = (uint64_t)poll
#endif
/* Loop */
void us_loop_free(struct us_loop_t *loop) {
us_internal_loop_data_free(loop);
close(loop->fd);
us_free(loop);
}
/* Poll */
struct us_poll_t *us_create_poll(struct us_loop_t *loop, int fallthrough, unsigned int ext_size) {
if (!fallthrough) {
loop->num_polls++;
}
return CLEAR_POINTER_TAG(us_malloc(sizeof(struct us_poll_t) + ext_size));
}
/* Todo: this one should be us_internal_poll_free */
void us_poll_free(struct us_poll_t *p, struct us_loop_t *loop) {
loop->num_polls--;
us_free(p);
}
void *us_poll_ext(struct us_poll_t *p) {
return p + 1;
}
/* Todo: why have us_poll_create AND us_poll_init!? libuv legacy! */
void us_poll_init(struct us_poll_t *p, LIBUS_SOCKET_DESCRIPTOR fd, int poll_type) {
p->state.fd = fd;
p->state.poll_type = poll_type;
}
int us_poll_events(struct us_poll_t *p) {
return ((p->state.poll_type & POLL_TYPE_POLLING_IN) ? LIBUS_SOCKET_READABLE : 0) | ((p->state.poll_type & POLL_TYPE_POLLING_OUT) ? LIBUS_SOCKET_WRITABLE : 0);
}
LIBUS_SOCKET_DESCRIPTOR us_poll_fd(struct us_poll_t *p) {
return p->state.fd;
}
/* Returns any of listen socket, socket, shut down socket or callback */
int us_internal_poll_type(struct us_poll_t *p) {
return p->state.poll_type & 3;
}
/* Bug: doesn't really SET, rather read and change, so needs to be inited first! */
void us_internal_poll_set_type(struct us_poll_t *p, int poll_type) {
p->state.poll_type = poll_type | (p->state.poll_type & 12);
}
/* Timer */
void *us_timer_ext(struct us_timer_t *timer) {
return ((struct us_internal_callback_t *) timer) + 1;
}
struct us_loop_t *us_timer_loop(struct us_timer_t *t) {
struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) t;
return internal_cb->loop;
}
/* Loop */
struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t *loop), void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop), unsigned int ext_size) {
struct us_loop_t *loop = (struct us_loop_t *) us_malloc(sizeof(struct us_loop_t) + ext_size);
loop->num_polls = 0;
/* These could be accessed if we close a poll before starting the loop */
loop->num_ready_polls = 0;
loop->current_ready_poll = 0;
loop->bun_polls = 0;
#ifdef LIBUS_USE_EPOLL
loop->fd = epoll_create1(EPOLL_CLOEXEC);
#else
loop->fd = kqueue();
#endif
us_internal_loop_data_init(loop, wakeup_cb, pre_cb, post_cb);
return loop;
}
void us_loop_run(struct us_loop_t *loop) {
us_loop_integrate(loop);
/* While we have non-fallthrough polls we shouldn't fall through */
while (loop->num_polls) {
/* Emit pre callback */
us_internal_loop_pre(loop);
/* Fetch ready polls */
#ifdef LIBUS_USE_EPOLL
loop->num_ready_polls = epoll_wait(loop->fd, loop->ready_polls, 1024, -1);
#else
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, NULL);
#endif
/* Iterate ready polls, dispatching them by type */
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
/* Any ready poll marked with nullptr will be ignored */
if (LIKELY(poll)) {
if (CLEAR_POINTER_TAG(poll) != poll) {
Bun__internal_dispatch_ready_poll(loop, poll);
continue;
}
#ifdef LIBUS_USE_EPOLL
int events = loop->ready_polls[loop->current_ready_poll].events;
int error = loop->ready_polls[loop->current_ready_poll].events & (EPOLLERR | EPOLLHUP);
#else
/* EVFILT_READ, EVFILT_TIME, EVFILT_USER are all mapped to LIBUS_SOCKET_READABLE */
int events = LIBUS_SOCKET_READABLE;
if (loop->ready_polls[loop->current_ready_poll].filter == EVFILT_WRITE) {
events = LIBUS_SOCKET_WRITABLE;
}
int error = loop->ready_polls[loop->current_ready_poll].flags & (EV_ERROR | EV_EOF);
#endif
/* Always filter all polls by what they actually poll for (callback polls always poll for readable) */
events &= us_poll_events(poll);
if (events || error) {
us_internal_dispatch_ready_poll(poll, error, events);
}
}
}
/* Emit post callback */
us_internal_loop_post(loop);
}
}
void us_loop_run_bun_tick(struct us_loop_t *loop, int64_t timeoutMs) {
us_loop_integrate(loop);
if (loop->num_polls == 0)
return;
/* Emit pre callback */
us_internal_loop_pre(loop);
/* Fetch ready polls */
#ifdef LIBUS_USE_EPOLL
if (timeoutMs > 0) {
loop->num_ready_polls = epoll_wait(loop->fd, loop->ready_polls, 1024, (int)timeoutMs);
} else {
loop->num_ready_polls = epoll_wait(loop->fd, loop->ready_polls, 1024, -1);
}
#else
if (timeoutMs > 0) {
struct timespec ts = {0, 0};
ts.tv_sec = timeoutMs / 1000;
ts.tv_nsec = (timeoutMs % 1000) * 1000000;
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, &ts);
} else {
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, NULL);
}
#endif
/* Iterate ready polls, dispatching them by type */
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
/* Any ready poll marked with nullptr will be ignored */
if (LIKELY(poll)) {
if (CLEAR_POINTER_TAG(poll) != poll) {
Bun__internal_dispatch_ready_poll(loop, poll);
continue;
}
#ifdef LIBUS_USE_EPOLL
int events = loop->ready_polls[loop->current_ready_poll].events;
int error = loop->ready_polls[loop->current_ready_poll].events & (EPOLLERR | EPOLLHUP);
#else
/* EVFILT_READ, EVFILT_TIME, EVFILT_USER are all mapped to LIBUS_SOCKET_READABLE */
int events = LIBUS_SOCKET_READABLE;
if (loop->ready_polls[loop->current_ready_poll].filter == EVFILT_WRITE) {
events = LIBUS_SOCKET_WRITABLE;
}
int error = loop->ready_polls[loop->current_ready_poll].flags & (EV_ERROR | EV_EOF);
#endif
/* Always filter all polls by what they actually poll for (callback polls always poll for readable) */
events &= us_poll_events(poll);
if (events || error) {
us_internal_dispatch_ready_poll(poll, error, events);
}
}
}
/* Emit post callback */
us_internal_loop_post(loop);
}
void us_internal_loop_update_pending_ready_polls(struct us_loop_t *loop, struct us_poll_t *old_poll, struct us_poll_t *new_poll, int old_events, int new_events) {
#ifdef LIBUS_USE_EPOLL
/* Epoll only has one ready poll per poll */
int num_entries_possibly_remaining = 1;
#else
/* Ready polls may contain same poll twice under kqueue, as one poll may hold two filters */
int num_entries_possibly_remaining = 2;//((old_events & LIBUS_SOCKET_READABLE) ? 1 : 0) + ((old_events & LIBUS_SOCKET_WRITABLE) ? 1 : 0);
#endif
/* Todo: for kqueue if we track things in us_change_poll it is possible to have a fast path with no seeking in cases of:
* current poll being us AND we only poll for one thing */
for (int i = loop->current_ready_poll; i < loop->num_ready_polls && num_entries_possibly_remaining; i++) {
if (GET_READY_POLL(loop, i) == old_poll) {
// if new events does not contain the ready events of this poll then remove (no we filter that out later on)
SET_READY_POLL(loop, i, new_poll);
num_entries_possibly_remaining--;
}
}
}
/* Poll */
#ifdef LIBUS_USE_KQUEUE
/* Helper function for setting or updating EVFILT_READ and EVFILT_WRITE */
int kqueue_change(int kqfd, int fd, int old_events, int new_events, void *user_data) {
struct kevent64_s change_list[2];
int change_length = 0;
/* Do they differ in readable? */
if ((new_events & LIBUS_SOCKET_READABLE) != (old_events & LIBUS_SOCKET_READABLE)) {
EV_SET64(&change_list[change_length++], fd, EVFILT_READ, (new_events & LIBUS_SOCKET_READABLE) ? EV_ADD : EV_DELETE, 0, 0, (uint64_t)(void*)user_data, 0, 0);
}
/* Do they differ in writable? */
if ((new_events & LIBUS_SOCKET_WRITABLE) != (old_events & LIBUS_SOCKET_WRITABLE)) {
EV_SET64(&change_list[change_length++], fd, EVFILT_WRITE, (new_events & LIBUS_SOCKET_WRITABLE) ? EV_ADD : EV_DELETE, 0, 0, (uint64_t)(void*)user_data, 0, 0);
}
int ret = kevent64(kqfd, change_list, change_length, NULL, 0, 0, NULL);
// ret should be 0 in most cases (not guaranteed when removing async)
return ret;
}
#endif
struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop, unsigned int ext_size) {
int events = us_poll_events(p);
struct us_poll_t *new_p = us_realloc(p, sizeof(struct us_poll_t) + ext_size);
if (p != new_p && events) {
#ifdef LIBUS_USE_EPOLL
/* Hack: forcefully update poll by stripping away already set events */
new_p->state.poll_type = us_internal_poll_type(new_p);
us_poll_change(new_p, loop, events);
#else
/* Forcefully update poll by resetting them with new_p as user data */
kqueue_change(loop->fd, new_p->state.fd, 0, events, new_p);
#endif
/* This is needed for epoll also (us_change_poll doesn't update the old poll) */
us_internal_loop_update_pending_ready_polls(loop, p, new_p, events, events);
}
return new_p;
}
void us_poll_start(struct us_poll_t *p, struct us_loop_t *loop, int events) {
p->state.poll_type = us_internal_poll_type(p) | ((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0) | ((events & LIBUS_SOCKET_WRITABLE) ? POLL_TYPE_POLLING_OUT : 0);
#ifdef LIBUS_USE_EPOLL
struct epoll_event event;
event.events = events;
event.data.ptr = p;
epoll_ctl(loop->fd, EPOLL_CTL_ADD, p->state.fd, &event);
#else
kqueue_change(loop->fd, p->state.fd, 0, events, p);
#endif
}
void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events) {
int old_events = us_poll_events(p);
if (old_events != events) {
p->state.poll_type = us_internal_poll_type(p) | ((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0) | ((events & LIBUS_SOCKET_WRITABLE) ? POLL_TYPE_POLLING_OUT : 0);
#ifdef LIBUS_USE_EPOLL
struct epoll_event event;
event.events = events;
event.data.ptr = p;
epoll_ctl(loop->fd, EPOLL_CTL_MOD, p->state.fd, &event);
#else
kqueue_change(loop->fd, p->state.fd, old_events, events, p);
#endif
/* Set all removed events to null-polls in pending ready poll list */
//us_internal_loop_update_pending_ready_polls(loop, p, p, old_events, events);
}
}
void us_poll_stop(struct us_poll_t *p, struct us_loop_t *loop) {
int old_events = us_poll_events(p);
int new_events = 0;
#ifdef LIBUS_USE_EPOLL
struct epoll_event event;
epoll_ctl(loop->fd, EPOLL_CTL_DEL, p->state.fd, &event);
#else
if (old_events) {
kqueue_change(loop->fd, p->state.fd, old_events, new_events, NULL);
}
#endif
/* Disable any instance of us in the pending ready poll list */
us_internal_loop_update_pending_ready_polls(loop, p, 0, old_events, new_events);
}
unsigned int us_internal_accept_poll_event(struct us_poll_t *p) {
#ifdef LIBUS_USE_EPOLL
int fd = us_poll_fd(p);
uint64_t buf;
int read_length = read(fd, &buf, 8);
(void)read_length;
return buf;
#else
/* Kqueue has no underlying FD for timers or user events */
return 0;
#endif
}
/* Timer */
#ifdef LIBUS_USE_EPOLL
struct us_timer_t *us_create_timer(struct us_loop_t *loop, int fallthrough, unsigned int ext_size) {
struct us_poll_t *p = us_create_poll(loop, fallthrough, sizeof(struct us_internal_callback_t) + ext_size);
int timerfd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK | TFD_CLOEXEC);
if (timerfd == -1) {
return NULL;
}
us_poll_init(p, timerfd, POLL_TYPE_CALLBACK);
struct us_internal_callback_t *cb = (struct us_internal_callback_t *) p;
cb->loop = loop;
cb->cb_expects_the_loop = 0;
cb->leave_poll_ready = 0;
return (struct us_timer_t *) cb;
}
#else
struct us_timer_t *us_create_timer(struct us_loop_t *loop, int fallthrough, unsigned int ext_size) {
struct us_internal_callback_t *cb = us_malloc(sizeof(struct us_internal_callback_t) + ext_size);
cb->loop = loop;
cb->cb_expects_the_loop = 0;
cb->leave_poll_ready = 0;
/* Bug: us_internal_poll_set_type does not SET the type, it only CHANGES it */
cb->p.state.poll_type = POLL_TYPE_POLLING_IN;
us_internal_poll_set_type((struct us_poll_t *) cb, POLL_TYPE_CALLBACK);
if (!fallthrough) {
loop->num_polls++;
}
return (struct us_timer_t *) cb;
}
#endif
#ifdef LIBUS_USE_EPOLL
void us_timer_close(struct us_timer_t *timer) {
struct us_internal_callback_t *cb = (struct us_internal_callback_t *) timer;
us_poll_stop(&cb->p, cb->loop);
close(us_poll_fd(&cb->p));
/* (regular) sockets are the only polls which are not freed immediately */
us_poll_free((struct us_poll_t *) timer, cb->loop);
}
void us_timer_set(struct us_timer_t *t, void (*cb)(struct us_timer_t *t), int ms, int repeat_ms) {
struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) t;
internal_cb->cb = (void (*)(struct us_internal_callback_t *)) cb;
struct itimerspec timer_spec = {
{repeat_ms / 1000, (long) (repeat_ms % 1000) * (long) 1000000},
{ms / 1000, (long) (ms % 1000) * (long) 1000000}
};
timerfd_settime(us_poll_fd((struct us_poll_t *) t), 0, &timer_spec, NULL);
us_poll_start((struct us_poll_t *) t, internal_cb->loop, LIBUS_SOCKET_READABLE);
}
#else
void us_timer_close(struct us_timer_t *timer) {
struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) timer;
struct kevent64_s event;
EV_SET64(&event, (uint64_t) (void*) internal_cb, EVFILT_TIMER, EV_DELETE, 0, 0, (uint64_t)internal_cb, 0, 0);
kevent64(internal_cb->loop->fd, &event, 1, NULL, 0, 0, NULL);
/* (regular) sockets are the only polls which are not freed immediately */
us_poll_free((struct us_poll_t *) timer, internal_cb->loop);
}
void us_timer_set(struct us_timer_t *t, void (*cb)(struct us_timer_t *t), int ms, int repeat_ms) {
struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) t;
internal_cb->cb = (void (*)(struct us_internal_callback_t *)) cb;
/* Bug: repeat_ms must be the same as ms, or 0 */
struct kevent64_s event;
uint64_t ptr = (uint64_t)(void*)internal_cb;
EV_SET64(&event, ptr, EVFILT_TIMER, EV_ADD | (repeat_ms ? 0 : EV_ONESHOT), 0, ms, (uint64_t)internal_cb, 0, 0);
kevent64(internal_cb->loop->fd, &event, 1, NULL, 0, 0, NULL);
}
#endif
/* Async (internal helper for loop's wakeup feature) */
#ifdef LIBUS_USE_EPOLL
struct us_internal_async *us_internal_create_async(struct us_loop_t *loop, int fallthrough, unsigned int ext_size) {
struct us_poll_t *p = us_create_poll(loop, fallthrough, sizeof(struct us_internal_callback_t) + ext_size);
us_poll_init(p, eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC), POLL_TYPE_CALLBACK);
struct us_internal_callback_t *cb = (struct us_internal_callback_t *) p;
cb->loop = loop;
cb->cb_expects_the_loop = 1;
cb->leave_poll_ready = 0;
return (struct us_internal_async *) cb;
}
// identical code as for timer, make it shared for "callback types"
void us_internal_async_close(struct us_internal_async *a) {
struct us_internal_callback_t *cb = (struct us_internal_callback_t *) a;
us_poll_stop(&cb->p, cb->loop);
close(us_poll_fd(&cb->p));
/* (regular) sockets are the only polls which are not freed immediately */
us_poll_free((struct us_poll_t *) a, cb->loop);
}
void us_internal_async_set(struct us_internal_async *a, void (*cb)(struct us_internal_async *)) {
struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) a;
internal_cb->cb = (void (*)(struct us_internal_callback_t *)) cb;
us_poll_start((struct us_poll_t *) a, internal_cb->loop, LIBUS_SOCKET_READABLE);
}
void us_internal_async_wakeup(struct us_internal_async *a) {
uint64_t one = 1;
int written = write(us_poll_fd((struct us_poll_t *) a), &one, 8);
(void)written;
}
#else
#define MACHPORT_BUF_LEN 1024
struct us_internal_async *us_internal_create_async(struct us_loop_t *loop, int fallthrough, unsigned int ext_size) {
struct us_internal_callback_t *cb = us_malloc(sizeof(struct us_internal_callback_t) + ext_size);
cb->loop = loop;
cb->cb_expects_the_loop = 1;
cb->leave_poll_ready = 0;
/* Bug: us_internal_poll_set_type does not SET the type, it only CHANGES it */
cb->p.state.poll_type = POLL_TYPE_POLLING_IN;
us_internal_poll_set_type((struct us_poll_t *) cb, POLL_TYPE_CALLBACK);
if (!fallthrough) {
loop->num_polls++;
}
cb->machport_buf = us_malloc(MACHPORT_BUF_LEN);
kern_return_t kr = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &cb->port);
if (UNLIKELY(kr != KERN_SUCCESS)) {
return NULL;
}
return (struct us_internal_async *) cb;
}
// identical code as for timer, make it shared for "callback types"
void us_internal_async_close(struct us_internal_async *a) {
struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) a;
struct kevent64_s event;
uint64_t ptr = (uint64_t)(void*)internal_cb;
EV_SET64(&event, ptr, EVFILT_MACHPORT, EV_DELETE, 0, 0, (uint64_t)(void*)internal_cb, 0,0);
kevent64(internal_cb->loop->fd, &event, 1, NULL, 0, 0, NULL);
mach_port_deallocate(mach_task_self(), internal_cb->port);
us_free(internal_cb->machport_buf);
/* (regular) sockets are the only polls which are not freed immediately */
us_poll_free((struct us_poll_t *) a, internal_cb->loop);
}
void us_internal_async_set(struct us_internal_async *a, void (*cb)(struct us_internal_async *)) {
struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) a;
internal_cb->cb = (void (*)(struct us_internal_callback_t *)) cb;
// EVFILT_MACHPORT benchmarks faster than EVFILT_USER when using multiple threads
// Very old versions of macOS required them to be portsets instead of ports
// but that is no longer the case
// There are not many examples on the internet of using machports this way
// you can find one in Chromium's codebase.
struct kevent64_s event;
event.ident = internal_cb->port;
event.filter = EVFILT_MACHPORT;
event.flags = EV_ADD | EV_ENABLE;
event.fflags = MACH_RCV_MSG | MACH_RCV_OVERWRITE;
event.ext[0] = (uint64_t)(void*)internal_cb->machport_buf;
event.ext[1] = MACHPORT_BUF_LEN;
event.udata = (uint64_t)(void*)internal_cb;
int ret = kevent64(internal_cb->loop->fd, &event, 1, NULL, 0, 0, NULL);
if (UNLIKELY(ret == -1)) {
abort();
}
}
void us_internal_async_wakeup(struct us_internal_async *a) {
struct us_internal_callback_t *internal_cb = (struct us_internal_callback_t *) a;
mach_msg_empty_send_t message;
memset(&message, 0, sizeof(message));
message.header.msgh_size = sizeof(message);
message.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_MAKE_SEND_ONCE);
message.header.msgh_remote_port = internal_cb->port;
kern_return_t kr = mach_msg_send(&message.header);
if (kr != KERN_SUCCESS) {
// If us_internal_async_wakeup is being called by other threads faster
// than the pump can dispatch work, the kernel message queue for the wakeup
// port can fill The kernel does return a SEND_ONCE right in the case of
// failure, which must be destroyed to avoid leaking.
mach_msg_destroy(&message.header);
}
}
#endif
#endif

View File

@@ -0,0 +1,72 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef EPOLL_KQUEUE_H
#define EPOLL_KQUEUE_H
#include "internal/loop_data.h"
#ifdef LIBUS_USE_EPOLL
#include <sys/epoll.h>
#include <sys/timerfd.h>
#include <sys/eventfd.h>
#define LIBUS_SOCKET_READABLE EPOLLIN
#define LIBUS_SOCKET_WRITABLE EPOLLOUT
#else
#include <sys/event.h>
/* Kqueue's EVFILT_ is NOT a bitfield, you cannot OR together them.
* We therefore have our own bitfield we then translate in every call */
#define LIBUS_SOCKET_READABLE 1
#define LIBUS_SOCKET_WRITABLE 2
#include <mach/mach.h>
#endif
struct us_loop_t {
alignas(LIBUS_EXT_ALIGNMENT) struct us_internal_loop_data_t data;
/* Number of non-fallthrough polls in the loop */
int num_polls;
/* Number of ready polls this iteration */
int num_ready_polls;
/* Current index in list of ready polls */
int current_ready_poll;
/* Loop's own file descriptor */
int fd;
/* Number of polls owned by bun */
unsigned int bun_polls;
/* The list of ready polls */
#ifdef LIBUS_USE_EPOLL
alignas(LIBUS_EXT_ALIGNMENT) struct epoll_event ready_polls[1024];
#else
alignas(LIBUS_EXT_ALIGNMENT) struct kevent64_s ready_polls[1024];
#endif
};
struct us_poll_t {
alignas(LIBUS_EXT_ALIGNMENT) struct {
signed int fd : 28; // we could have this unsigned if we wanted to, -1 should never be used
unsigned int poll_type : 4;
} state;
};
#endif // EPOLL_KQUEUE_H

View File

@@ -0,0 +1,245 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef INTERNAL_H
#define INTERNAL_H
#if defined(_MSC_VER)
#define alignas(x) __declspec(align(x))
#else
#include <stdalign.h>
#endif
#if defined(LIBUS_USE_KQUEUE)
#include <mach/mach.h>
#endif
#if defined(LIBUS_USE_EPOLL) || defined(LIBUS_USE_KQUEUE)
#define LIBUS_MAX_READY_POLLS 1024
void us_internal_loop_update_pending_ready_polls(struct us_loop_t *loop, struct us_poll_t *old_poll, struct us_poll_t *new_poll, int old_events, int new_events);
#endif
/* We only have one networking implementation so far */
#include "internal/networking/bsd.h"
/* We have many different eventing implementations */
#if defined(LIBUS_USE_EPOLL) || defined(LIBUS_USE_KQUEUE)
#include "internal/eventing/epoll_kqueue.h"
#endif
/* Poll type and what it polls for */
enum {
/* Two first bits */
POLL_TYPE_SOCKET = 0,
POLL_TYPE_SOCKET_SHUT_DOWN = 1,
POLL_TYPE_SEMI_SOCKET = 2,
POLL_TYPE_CALLBACK = 3,
/* Two last bits */
POLL_TYPE_POLLING_OUT = 4,
POLL_TYPE_POLLING_IN = 8
};
/* Loop related */
void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int events);
void us_internal_timer_sweep(struct us_loop_t *loop);
void us_internal_free_closed_sockets(struct us_loop_t *loop);
void us_internal_loop_link(struct us_loop_t *loop, struct us_socket_context_t *context);
void us_internal_loop_unlink(struct us_loop_t *loop, struct us_socket_context_t *context);
void us_internal_loop_data_init(struct us_loop_t *loop, void (*wakeup_cb)(struct us_loop_t *loop),
void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop));
void us_internal_loop_data_free(struct us_loop_t *loop);
void us_internal_loop_pre(struct us_loop_t *loop);
void us_internal_loop_post(struct us_loop_t *loop);
/* Asyncs (old) */
struct us_internal_async *us_internal_create_async(struct us_loop_t *loop, int fallthrough, unsigned int ext_size);
void us_internal_async_close(struct us_internal_async *a);
void us_internal_async_set(struct us_internal_async *a, void (*cb)(struct us_internal_async *));
void us_internal_async_wakeup(struct us_internal_async *a);
/* Eventing related */
unsigned int us_internal_accept_poll_event(struct us_poll_t *p);
int us_internal_poll_type(struct us_poll_t *p);
void us_internal_poll_set_type(struct us_poll_t *p, int poll_type);
/* SSL loop data */
void us_internal_init_loop_ssl_data(struct us_loop_t *loop);
void us_internal_free_loop_ssl_data(struct us_loop_t *loop);
/* Socket context related */
void us_internal_socket_context_link_socket(struct us_socket_context_t *context, struct us_socket_t *s);
void us_internal_socket_context_unlink_socket(struct us_socket_context_t *context, struct us_socket_t *s);
/* Sockets are polls */
struct us_socket_t {
alignas(LIBUS_EXT_ALIGNMENT) struct us_poll_t p; // 4 bytes
unsigned char timeout; // 1 byte
unsigned char long_timeout; // 1 byte
unsigned short low_prio_state; /* 0 = not in low-prio queue, 1 = is in low-prio queue, 2 = was in low-prio queue in this iteration */
struct us_socket_context_t *context;
struct us_socket_t *prev, *next;
};
struct us_wrapped_socket_context_t {
struct us_socket_events_t events;
struct us_socket_events_t old_events;
};
#if defined(LIBUS_USE_KQUEUE)
/* Internal callback types are polls just like sockets */
struct us_internal_callback_t {
alignas(LIBUS_EXT_ALIGNMENT) struct us_poll_t p;
struct us_loop_t *loop;
int cb_expects_the_loop;
int leave_poll_ready;
void (*cb)(struct us_internal_callback_t *cb);
mach_port_t port;
void* machport_buf;
};
#else
struct us_internal_callback_t {
alignas(LIBUS_EXT_ALIGNMENT) struct us_poll_t p;
struct us_loop_t *loop;
int cb_expects_the_loop;
int leave_poll_ready;
void (*cb)(struct us_internal_callback_t *cb);
};
#endif
/* Listen sockets are sockets */
struct us_listen_socket_t {
alignas(LIBUS_EXT_ALIGNMENT) struct us_socket_t s;
unsigned int socket_ext_size;
};
/* Listen sockets are keps in their own list */
void us_internal_socket_context_link_listen_socket(struct us_socket_context_t *context, struct us_listen_socket_t *s);
void us_internal_socket_context_unlink_listen_socket(struct us_socket_context_t *context, struct us_listen_socket_t *s);
struct us_socket_context_t {
alignas(LIBUS_EXT_ALIGNMENT) struct us_loop_t *loop;
uint32_t global_tick;
unsigned char timestamp;
unsigned char long_timestamp;
struct us_socket_t *head_sockets;
struct us_listen_socket_t *head_listen_sockets;
struct us_socket_t *iterator;
struct us_socket_context_t *prev, *next;
struct us_socket_t *(*on_open)(struct us_socket_t *, int is_client, char *ip, int ip_length);
struct us_socket_t *(*on_data)(struct us_socket_t *, char *data, int length);
struct us_socket_t *(*on_writable)(struct us_socket_t *);
struct us_socket_t *(*on_close)(struct us_socket_t *, int code, void *reason);
//void (*on_timeout)(struct us_socket_context *);
struct us_socket_t *(*on_socket_timeout)(struct us_socket_t *);
struct us_socket_t *(*on_socket_long_timeout)(struct us_socket_t *);
struct us_socket_t *(*on_end)(struct us_socket_t *);
struct us_socket_t *(*on_connect_error)(struct us_socket_t *, int code);
int (*is_low_prio)(struct us_socket_t *);
};
/* Internal SSL interface */
#ifndef LIBUS_NO_SSL
struct us_internal_ssl_socket_context_t;
struct us_internal_ssl_socket_t;
/* SNI functions */
void us_internal_ssl_socket_context_add_server_name(struct us_internal_ssl_socket_context_t *context, const char *hostname_pattern, struct us_socket_context_options_t options, void *user);
void us_bun_internal_ssl_socket_context_add_server_name(struct us_internal_ssl_socket_context_t *context, const char *hostname_pattern, struct us_bun_socket_context_options_t options, void *user);
void us_internal_ssl_socket_context_remove_server_name(struct us_internal_ssl_socket_context_t *context, const char *hostname_pattern);
void us_internal_ssl_socket_context_on_server_name(struct us_internal_ssl_socket_context_t *context, void (*cb)(struct us_internal_ssl_socket_context_t *, const char *));
void *us_internal_ssl_socket_get_sni_userdata(struct us_internal_ssl_socket_t *s);
void *us_internal_ssl_socket_context_find_server_name_userdata(struct us_internal_ssl_socket_context_t *context, const char *hostname_pattern);
void *us_internal_ssl_socket_get_native_handle(struct us_internal_ssl_socket_t *s);
void *us_internal_ssl_socket_context_get_native_handle(struct us_internal_ssl_socket_context_t *context);
struct us_bun_verify_error_t us_internal_verify_error(struct us_internal_ssl_socket_t *s);
struct us_internal_ssl_socket_context_t *us_internal_create_ssl_socket_context(struct us_loop_t *loop,
int context_ext_size, struct us_socket_context_options_t options);
struct us_internal_ssl_socket_context_t *us_internal_bun_create_ssl_socket_context(struct us_loop_t *loop,
int context_ext_size, struct us_bun_socket_context_options_t options);
void us_internal_ssl_socket_context_free(struct us_internal_ssl_socket_context_t *context);
void us_internal_ssl_socket_context_on_open(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_open)(struct us_internal_ssl_socket_t *s, int is_client, char *ip, int ip_length));
void us_internal_ssl_socket_context_on_close(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_close)(struct us_internal_ssl_socket_t *s, int code, void *reason));
void us_internal_ssl_socket_context_on_data(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_data)(struct us_internal_ssl_socket_t *s, char *data, int length));
void us_internal_ssl_handshake(struct us_internal_ssl_socket_t *s, void (*on_handshake)(struct us_internal_ssl_socket_t *, int success, struct us_bun_verify_error_t verify_error, void* custom_data), void* custom_data);
void us_internal_on_ssl_handshake(struct us_internal_ssl_socket_context_t * context, void (*on_handshake)(struct us_internal_ssl_socket_t *, int success, struct us_bun_verify_error_t verify_error, void* custom_data), void* custom_data);
void us_internal_ssl_socket_context_on_writable(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_writable)(struct us_internal_ssl_socket_t *s));
void us_internal_ssl_socket_context_on_timeout(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_timeout)(struct us_internal_ssl_socket_t *s));
void us_internal_ssl_socket_context_on_long_timeout(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_timeout)(struct us_internal_ssl_socket_t *s));
void us_internal_ssl_socket_context_on_end(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_end)(struct us_internal_ssl_socket_t *s));
void us_internal_ssl_socket_context_on_connect_error(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *(*on_connect_error)(struct us_internal_ssl_socket_t *s, int code));
struct us_listen_socket_t *us_internal_ssl_socket_context_listen(struct us_internal_ssl_socket_context_t *context,
const char *host, int port, int options, int socket_ext_size);
struct us_listen_socket_t *us_internal_ssl_socket_context_listen_unix(struct us_internal_ssl_socket_context_t *context,
const char *path, int options, int socket_ext_size);
struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect(struct us_internal_ssl_socket_context_t *context,
const char *host, int port, const char *source_host, int options, int socket_ext_size);
struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_connect_unix(struct us_internal_ssl_socket_context_t *context,
const char *server_path, int options, int socket_ext_size);
int us_internal_ssl_socket_write(struct us_internal_ssl_socket_t *s, const char *data, int length, int msg_more);
int us_internal_ssl_socket_raw_write(struct us_internal_ssl_socket_t *s, const char *data, int length, int msg_more);
void us_internal_ssl_socket_timeout(struct us_internal_ssl_socket_t *s, unsigned int seconds);
void *us_internal_ssl_socket_context_ext(struct us_internal_ssl_socket_context_t *s);
struct us_internal_ssl_socket_context_t *us_internal_ssl_socket_get_context(struct us_internal_ssl_socket_t *s);
void *us_internal_ssl_socket_ext(struct us_internal_ssl_socket_t *s);
int us_internal_ssl_socket_is_shut_down(struct us_internal_ssl_socket_t *s);
void us_internal_ssl_socket_shutdown(struct us_internal_ssl_socket_t *s);
struct us_internal_ssl_socket_t *us_internal_ssl_socket_context_adopt_socket(struct us_internal_ssl_socket_context_t *context,
struct us_internal_ssl_socket_t *s, int ext_size);
struct us_internal_ssl_socket_t *us_internal_ssl_socket_wrap_with_tls(struct us_socket_t *s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int socket_ext_size);
struct us_internal_ssl_socket_context_t *us_internal_create_child_ssl_socket_context(struct us_internal_ssl_socket_context_t *context, int context_ext_size);
struct us_loop_t *us_internal_ssl_socket_context_loop(struct us_internal_ssl_socket_context_t *context);
struct us_internal_ssl_socket_t* us_internal_ssl_socket_open(struct us_internal_ssl_socket_t * s, int is_client, char* ip, int ip_length);
int us_raw_root_certs(struct us_cert_string_t**out);
#endif
#endif // INTERNAL_H

View File

@@ -0,0 +1,38 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef LOOP_DATA_H
#define LOOP_DATA_H
struct us_internal_loop_data_t {
struct us_timer_t *sweep_timer;
struct us_internal_async *wakeup_async;
int last_write_failed;
struct us_socket_context_t *head;
struct us_socket_context_t *iterator;
char *recv_buf;
void *ssl_data;
void (*pre_cb)(struct us_loop_t *);
void (*post_cb)(struct us_loop_t *);
struct us_socket_t *closed_head;
struct us_socket_t *low_prio_head;
int low_prio_budget;
/* We do not care if this flips or not, it doesn't matter */
long long iteration_nr;
};
#endif // LOOP_DATA_H

View File

@@ -0,0 +1,108 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef BSD_H
#define BSD_H
// top-most wrapper of bsd-like syscalls
// holds everything you need from the bsd/winsock interfaces, only included by internal libusockets.h
// here everything about the syscalls are inline-wrapped and included
#include "libusockets.h"
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define SETSOCKOPT_PTR_TYPE const char *
#define LIBUS_SOCKET_ERROR INVALID_SOCKET
#else
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
/* For socklen_t */
#include <sys/socket.h>
#define SETSOCKOPT_PTR_TYPE int *
#define LIBUS_SOCKET_ERROR -1
#endif
#define LIBUS_UDP_MAX_SIZE (64 * 1024)
#define LIBUS_UDP_MAX_NUM 1024
struct bsd_addr_t {
struct sockaddr_storage mem;
socklen_t len;
char *ip;
int ip_length;
int port;
};
int bsd_sendmmsg(LIBUS_SOCKET_DESCRIPTOR fd, void *msgvec, unsigned int vlen, int flags);
int bsd_recvmmsg(LIBUS_SOCKET_DESCRIPTOR fd, void *msgvec, unsigned int vlen, int flags, void *timeout);
int bsd_udp_packet_buffer_payload_length(void *msgvec, int index);
char *bsd_udp_packet_buffer_payload(void *msgvec, int index);
char *bsd_udp_packet_buffer_peer(void *msgvec, int index);
int bsd_udp_packet_buffer_local_ip(void *msgvec, int index, char *ip);
int bsd_udp_packet_buffer_ecn(void *msgvec, int index);
void *bsd_create_udp_packet_buffer();
void bsd_udp_buffer_set_packet_payload(struct us_udp_packet_buffer_t *send_buf, int index, int offset, void *payload, int length, void *peer_addr);
LIBUS_SOCKET_DESCRIPTOR apple_no_sigpipe(LIBUS_SOCKET_DESCRIPTOR fd);
LIBUS_SOCKET_DESCRIPTOR bsd_set_nonblocking(LIBUS_SOCKET_DESCRIPTOR fd);
void bsd_socket_nodelay(LIBUS_SOCKET_DESCRIPTOR fd, int enabled);
void bsd_socket_flush(LIBUS_SOCKET_DESCRIPTOR fd);
LIBUS_SOCKET_DESCRIPTOR bsd_create_socket(int domain, int type, int protocol);
void bsd_close_socket(LIBUS_SOCKET_DESCRIPTOR fd);
void bsd_shutdown_socket(LIBUS_SOCKET_DESCRIPTOR fd);
void bsd_shutdown_socket_read(LIBUS_SOCKET_DESCRIPTOR fd);
void internal_finalize_bsd_addr(struct bsd_addr_t *addr);
int bsd_local_addr(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd_addr_t *addr);
int bsd_remote_addr(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd_addr_t *addr);
char *bsd_addr_get_ip(struct bsd_addr_t *addr);
int bsd_addr_get_ip_length(struct bsd_addr_t *addr);
int bsd_addr_get_port(struct bsd_addr_t *addr);
// called by dispatch_ready_poll
LIBUS_SOCKET_DESCRIPTOR bsd_accept_socket(LIBUS_SOCKET_DESCRIPTOR fd, struct bsd_addr_t *addr);
int bsd_recv(LIBUS_SOCKET_DESCRIPTOR fd, void *buf, int length, int flags);
int bsd_send(LIBUS_SOCKET_DESCRIPTOR fd, const char *buf, int length, int msg_more);
int bsd_would_block();
// return LIBUS_SOCKET_ERROR or the fd that represents listen socket
// listen both on ipv6 and ipv4
LIBUS_SOCKET_DESCRIPTOR bsd_create_listen_socket(const char *host, int port, int options);
LIBUS_SOCKET_DESCRIPTOR bsd_create_listen_socket_unix(const char *path, int options);
/* Creates an UDP socket bound to the hostname and port */
LIBUS_SOCKET_DESCRIPTOR bsd_create_udp_socket(const char *host, int port);
LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket(const char *host, int port, const char *source_host, int options);
LIBUS_SOCKET_DESCRIPTOR bsd_create_connect_socket_unix(const char *server_path, int options);
#endif // BSD_H

View File

@@ -0,0 +1,408 @@
/*
* Authored by Alex Hultman, 2018-2019.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef us_malloc
#define us_malloc malloc
#endif
#ifndef us_realloc
#define us_realloc realloc
#endif
#ifndef us_free
#define us_free free
#endif
#ifndef LIBUSOCKETS_H
#define LIBUSOCKETS_H
/* 512kb shared receive buffer */
#define LIBUS_RECV_BUFFER_LENGTH 524288
/* A timeout granularity of 4 seconds means give or take 4 seconds from set timeout */
#define LIBUS_TIMEOUT_GRANULARITY 4
/* 32 byte padding of receive buffer ends */
#define LIBUS_RECV_BUFFER_PADDING 32
/* Guaranteed alignment of extension memory */
#define LIBUS_EXT_ALIGNMENT 16
/* Define what a socket descriptor is based on platform */
#ifdef _WIN32
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <winsock2.h>
#define LIBUS_SOCKET_DESCRIPTOR SOCKET
#else
#define LIBUS_SOCKET_DESCRIPTOR int
#endif
#include "stddef.h"
#ifdef __cplusplus
extern "C" {
#endif
enum {
/* No meaning, default listen option */
LIBUS_LISTEN_DEFAULT,
/* We exclusively own this port, do not share it */
LIBUS_LISTEN_EXCLUSIVE_PORT
};
/* Library types publicly available */
struct us_socket_t;
struct us_timer_t;
struct us_socket_context_t;
struct us_loop_t;
struct us_poll_t;
struct us_udp_socket_t;
struct us_udp_packet_buffer_t;
struct us_cert_string_t {
const char* str;
size_t len;
};
/* Public interface for UDP sockets */
/* Peeks data and length of UDP payload */
char *us_udp_packet_buffer_payload(struct us_udp_packet_buffer_t *buf, int index);
int us_udp_packet_buffer_payload_length(struct us_udp_packet_buffer_t *buf, int index);
/* Copies out local (received destination) ip (4 or 16 bytes) of received packet */
int us_udp_packet_buffer_local_ip(struct us_udp_packet_buffer_t *buf, int index, char *ip);
/* Get the bound port in host byte order */
int us_udp_socket_bound_port(struct us_udp_socket_t *s);
/* Peeks peer addr (sockaddr) of received packet */
char *us_udp_packet_buffer_peer(struct us_udp_packet_buffer_t *buf, int index);
/* Peeks ECN of received packet */
int us_udp_packet_buffer_ecn(struct us_udp_packet_buffer_t *buf, int index);
/* Receives a set of packets into specified packet buffer */
int us_udp_socket_receive(struct us_udp_socket_t *s, struct us_udp_packet_buffer_t *buf);
void us_udp_buffer_set_packet_payload(struct us_udp_packet_buffer_t *send_buf, int index, int offset, void *payload, int length, void *peer_addr);
int us_udp_socket_send(struct us_udp_socket_t *s, struct us_udp_packet_buffer_t *buf, int num);
/* Allocates a packet buffer that is reuable per thread. Mutated by us_udp_socket_receive. */
struct us_udp_packet_buffer_t *us_create_udp_packet_buffer();
/* Creates a (heavy-weight) UDP socket with a user space ring buffer. Again, this one is heavy weight and
* shoud be reused. One entire QUIC server can be implemented using only one single UDP socket so weight
* is not a concern as is the case for TCP sockets which are 1-to-1 with TCP connections. */
//struct us_udp_socket_t *us_create_udp_socket(struct us_loop_t *loop, void (*read_cb)(struct us_udp_socket_t *), unsigned short port);
//struct us_udp_socket_t *us_create_udp_socket(struct us_loop_t *loop, void (*data_cb)(struct us_udp_socket_t *, struct us_udp_packet_buffer_t *, int), void (*drain_cb)(struct us_udp_socket_t *), char *host, unsigned short port);
struct us_udp_socket_t *us_create_udp_socket(struct us_loop_t *loop, struct us_udp_packet_buffer_t *buf, void (*data_cb)(struct us_udp_socket_t *, struct us_udp_packet_buffer_t *, int), void (*drain_cb)(struct us_udp_socket_t *), const char *host, unsigned short port, void *user);
/* This one is ugly, should be ext! not user */
void *us_udp_socket_user(struct us_udp_socket_t *s);
/* Binds the UDP socket to an interface and port */
int us_udp_socket_bind(struct us_udp_socket_t *s, const char *hostname, unsigned int port);
/* Public interfaces for timers */
/* Create a new high precision, low performance timer. May fail and return null */
struct us_timer_t *us_create_timer(struct us_loop_t *loop, int fallthrough, unsigned int ext_size);
/* Returns user data extension for this timer */
void *us_timer_ext(struct us_timer_t *timer);
/* */
void us_timer_close(struct us_timer_t *timer);
/* Arm a timer with a delay from now and eventually a repeat delay.
* Specify 0 as repeat delay to disable repeating. Specify both 0 to disarm. */
void us_timer_set(struct us_timer_t *timer, void (*cb)(struct us_timer_t *t), int ms, int repeat_ms);
/* Returns the loop for this timer */
struct us_loop_t *us_timer_loop(struct us_timer_t *t);
/* Public interfaces for contexts */
struct us_socket_context_options_t {
const char *key_file_name;
const char *cert_file_name;
const char *passphrase;
const char *dh_params_file_name;
const char *ca_file_name;
const char *ssl_ciphers;
int ssl_prefer_low_memory_usage; /* Todo: rename to prefer_low_memory_usage and apply for TCP as well */
};
struct us_socket_events_t {
struct us_socket_t *(*on_open)(struct us_socket_t *, int is_client, char *ip, int ip_length);
struct us_socket_t *(*on_data)(struct us_socket_t *, char *data, int length);
struct us_socket_t *(*on_writable)(struct us_socket_t *);
struct us_socket_t *(*on_close)(struct us_socket_t *, int code, void *reason);
//void (*on_timeout)(struct us_socket_context *);
struct us_socket_t *(*on_timeout)(struct us_socket_t *);
struct us_socket_t *(*on_long_timeout)(struct us_socket_t *);
struct us_socket_t *(*on_end)(struct us_socket_t *);
struct us_socket_t *(*on_connect_error)(struct us_socket_t *, int code);
void (*on_handshake)(struct us_socket_t*, int success, struct us_bun_verify_error_t verify_error, void* custom_data);
};
struct us_bun_verify_error_t {
long error;
const char* code;
const char* reason;
};
struct us_bun_socket_context_options_t {
const char *key_file_name;
const char *cert_file_name;
const char *passphrase;
const char *dh_params_file_name;
const char *ca_file_name;
const char *ssl_ciphers;
int ssl_prefer_low_memory_usage; /* Todo: rename to prefer_low_memory_usage and apply for TCP as well */
const char **key;
unsigned int key_count;
const char **cert;
unsigned int cert_count;
const char **ca;
unsigned int ca_count;
unsigned int secure_options;
int reject_unauthorized;
int request_cert;
};
/* Return 15-bit timestamp for this context */
unsigned short us_socket_context_timestamp(int ssl, struct us_socket_context_t *context);
/* Adds SNI domain and cert in asn1 format */
void us_socket_context_add_server_name(int ssl, struct us_socket_context_t *context, const char *hostname_pattern, struct us_socket_context_options_t options, void *user);
void us_bun_socket_context_add_server_name(int ssl, struct us_socket_context_t *context, const char *hostname_pattern, struct us_bun_socket_context_options_t options, void *user);
void us_socket_context_remove_server_name(int ssl, struct us_socket_context_t *context, const char *hostname_pattern);
void us_socket_context_on_server_name(int ssl, struct us_socket_context_t *context, void (*cb)(struct us_socket_context_t *, const char *hostname));
void *us_socket_server_name_userdata(int ssl, struct us_socket_t *s);
void *us_socket_context_find_server_name_userdata(int ssl, struct us_socket_context_t *context, const char *hostname_pattern);
/* Returns the underlying SSL native handle, such as SSL_CTX or nullptr */
void *us_socket_context_get_native_handle(int ssl, struct us_socket_context_t *context);
/* A socket context holds shared callbacks and user data extension for associated sockets */
struct us_socket_context_t *us_create_socket_context(int ssl, struct us_loop_t *loop,
int ext_size, struct us_socket_context_options_t options);
struct us_socket_context_t *us_create_bun_socket_context(int ssl, struct us_loop_t *loop,
int ext_size, struct us_bun_socket_context_options_t options);
/* Delete resources allocated at creation time. */
void us_socket_context_free(int ssl, struct us_socket_context_t *context);
struct us_bun_verify_error_t us_socket_verify_error(int ssl, struct us_socket_t *context);
/* Setters of various async callbacks */
void us_socket_context_on_open(int ssl, struct us_socket_context_t *context,
struct us_socket_t *(*on_open)(struct us_socket_t *s, int is_client, char *ip, int ip_length));
void us_socket_context_on_close(int ssl, struct us_socket_context_t *context,
struct us_socket_t *(*on_close)(struct us_socket_t *s, int code, void *reason));
void us_socket_context_on_data(int ssl, struct us_socket_context_t *context,
struct us_socket_t *(*on_data)(struct us_socket_t *s, char *data, int length));
void us_socket_context_on_writable(int ssl, struct us_socket_context_t *context,
struct us_socket_t *(*on_writable)(struct us_socket_t *s));
void us_socket_context_on_timeout(int ssl, struct us_socket_context_t *context,
struct us_socket_t *(*on_timeout)(struct us_socket_t *s));
void us_socket_context_on_long_timeout(int ssl, struct us_socket_context_t *context,
struct us_socket_t *(*on_timeout)(struct us_socket_t *s));
/* This one is only used for when a connecting socket fails in a late stage. */
void us_socket_context_on_connect_error(int ssl, struct us_socket_context_t *context,
struct us_socket_t *(*on_connect_error)(struct us_socket_t *s, int code));
void us_socket_context_on_handshake(int ssl, struct us_socket_context_t *context, void (*on_handshake)(struct us_socket_context_t *, int success, struct us_bun_verify_error_t verify_error, void* custom_data), void* custom_data);
/* Emitted when a socket has been half-closed */
void us_socket_context_on_end(int ssl, struct us_socket_context_t *context, struct us_socket_t *(*on_end)(struct us_socket_t *s));
/* Returns user data extension for this socket context */
void *us_socket_context_ext(int ssl, struct us_socket_context_t *context);
/* Closes all open sockets, including listen sockets. Does not invalidate the socket context. */
void us_socket_context_close(int ssl, struct us_socket_context_t *context);
/* Listen for connections. Acts as the main driving cog in a server. Will call set async callbacks. */
struct us_listen_socket_t *us_socket_context_listen(int ssl, struct us_socket_context_t *context,
const char *host, int port, int options, int socket_ext_size);
struct us_listen_socket_t *us_socket_context_listen_unix(int ssl, struct us_socket_context_t *context,
const char *path, int options, int socket_ext_size);
/* listen_socket.c/.h */
void us_listen_socket_close(int ssl, struct us_listen_socket_t *ls);
/* Land in on_open or on_connection_error or return null or return socket */
struct us_socket_t *us_socket_context_connect(int ssl, struct us_socket_context_t *context,
const char *host, int port, const char *source_host, int options, int socket_ext_size);
struct us_socket_t *us_socket_context_connect_unix(int ssl, struct us_socket_context_t *context,
const char *server_path, int options, int socket_ext_size);
/* Is this socket established? Can be used to check if a connecting socket has fired the on_open event yet.
* Can also be used to determine if a socket is a listen_socket or not, but you probably know that already. */
int us_socket_is_established(int ssl, struct us_socket_t *s);
/* Cancel a connecting socket. Can be used together with us_socket_timeout to limit connection times.
* Entirely destroys the socket - this function works like us_socket_close but does not trigger on_close event since
* you never got the on_open event first. */
struct us_socket_t *us_socket_close_connecting(int ssl, struct us_socket_t *s);
/* Returns the loop for this socket context. */
struct us_loop_t *us_socket_context_loop(int ssl, struct us_socket_context_t *context);
/* Invalidates passed socket, returning a new resized socket which belongs to a different socket context.
* Used mainly for "socket upgrades" such as when transitioning from HTTP to WebSocket. */
struct us_socket_t *us_socket_context_adopt_socket(int ssl, struct us_socket_context_t *context, struct us_socket_t *s, int ext_size);
/* Create a child socket context which acts much like its own socket context with its own callbacks yet still relies on the
* parent socket context for some shared resources. Child socket contexts should be used together with socket adoptions and nothing else. */
struct us_socket_context_t *us_create_child_socket_context(int ssl, struct us_socket_context_t *context, int context_ext_size);
/* Public interfaces for loops */
/* Returns a new event loop with user data extension */
struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t *loop),
void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop), unsigned int ext_size);
/* Frees the loop immediately */
void us_loop_free(struct us_loop_t *loop);
/* Returns the loop user data extension */
void *us_loop_ext(struct us_loop_t *loop);
/* Blocks the calling thread and drives the event loop until no more non-fallthrough polls are scheduled */
void us_loop_run(struct us_loop_t *loop);
/* Signals the loop from any thread to wake up and execute its wakeup handler from the loop's own running thread.
* This is the only fully thread-safe function and serves as the basis for thread safety */
void us_wakeup_loop(struct us_loop_t *loop);
/* Hook up timers in existing loop */
void us_loop_integrate(struct us_loop_t *loop);
/* Returns the loop iteration number */
long long us_loop_iteration_number(struct us_loop_t *loop);
/* Public interfaces for polls */
/* A fallthrough poll does not keep the loop running, it falls through */
struct us_poll_t *us_create_poll(struct us_loop_t *loop, int fallthrough, unsigned int ext_size);
/* After stopping a poll you must manually free the memory */
void us_poll_free(struct us_poll_t *p, struct us_loop_t *loop);
/* Associate this poll with a socket descriptor and poll type */
void us_poll_init(struct us_poll_t *p, LIBUS_SOCKET_DESCRIPTOR fd, int poll_type);
/* Start, change and stop polling for events */
void us_poll_start(struct us_poll_t *p, struct us_loop_t *loop, int events);
void us_poll_change(struct us_poll_t *p, struct us_loop_t *loop, int events);
void us_poll_stop(struct us_poll_t *p, struct us_loop_t *loop);
/* Return what events we are polling for */
int us_poll_events(struct us_poll_t *p);
/* Returns the user data extension of this poll */
void *us_poll_ext(struct us_poll_t *p);
/* Get associated socket descriptor from a poll */
LIBUS_SOCKET_DESCRIPTOR us_poll_fd(struct us_poll_t *p);
/* Resize an active poll */
struct us_poll_t *us_poll_resize(struct us_poll_t *p, struct us_loop_t *loop, unsigned int ext_size);
/* Public interfaces for sockets */
/* Returns the underlying native handle for a socket, such as SSL or file descriptor.
* In the case of file descriptor, the value of pointer is fd. */
void *us_socket_get_native_handle(int ssl, struct us_socket_t *s);
/* Write up to length bytes of data. Returns actual bytes written.
* Will call the on_writable callback of active socket context on failure to write everything off in one go.
* Set hint msg_more if you have more immediate data to write. */
int us_socket_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more);
/* Set a low precision, high performance timer on a socket. A socket can only have one single active timer
* at any given point in time. Will remove any such pre set timer */
void us_socket_timeout(int ssl, struct us_socket_t *s, unsigned int seconds);
/* Set a low precision, high performance timer on a socket. Suitable for per-minute precision. */
void us_socket_long_timeout(int ssl, struct us_socket_t *s, unsigned int minutes);
/* Return the user data extension of this socket */
void *us_socket_ext(int ssl, struct us_socket_t *s);
/* Return the socket context of this socket */
struct us_socket_context_t *us_socket_context(int ssl, struct us_socket_t *s);
/* Withdraw any msg_more status and flush any pending data */
void us_socket_flush(int ssl, struct us_socket_t *s);
/* Shuts down the connection by sending FIN and/or close_notify */
void us_socket_shutdown(int ssl, struct us_socket_t *s);
/* Shuts down the connection in terms of read, meaning next event loop
* iteration will catch the socket being closed. Can be used to defer closing
* to next event loop iteration. */
void us_socket_shutdown_read(int ssl, struct us_socket_t *s);
/* Returns whether the socket has been shut down or not */
int us_socket_is_shut_down(int ssl, struct us_socket_t *s);
/* Returns whether this socket has been closed. Only valid if memory has not yet been released. */
int us_socket_is_closed(int ssl, struct us_socket_t *s);
/* Immediately closes the socket */
struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s, int code, void *reason);
/* Returns local port or -1 on failure. */
int us_socket_local_port(int ssl, struct us_socket_t *s);
/* Copy remote (IP) address of socket, or fail with zero length. */
void us_socket_remote_address(int ssl, struct us_socket_t *s, char *buf, int *length);
/* Bun extras */
struct us_socket_t *us_socket_detach(int ssl, struct us_socket_t *s);
struct us_socket_t *us_socket_attach(int ssl, LIBUS_SOCKET_DESCRIPTOR client_fd, struct us_socket_context_t *ctx, int flags, int socket_ext_size);
struct us_socket_t *us_socket_wrap_with_tls(int ssl, struct us_socket_t *s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int socket_ext_size);
int us_socket_raw_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more);
struct us_socket_t* us_socket_open(int ssl, struct us_socket_t * s, int is_client, char* ip, int ip_length);
int us_raw_root_certs(struct us_cert_string_t**out);
#ifdef __cplusplus
}
#endif
/* Decide what eventing system to use by default */
#if !defined(LIBUS_USE_EPOLL) && !defined(LIBUS_USE_LIBUV) && !defined(LIBUS_USE_GCD) && !defined(LIBUS_USE_KQUEUE) && !defined(LIBUS_USE_ASIO)
#if defined(_WIN32)
#define LIBUS_USE_LIBUV
#elif defined(__APPLE__) || defined(__FreeBSD__)
#define LIBUS_USE_KQUEUE
#else
#define LIBUS_USE_EPOLL
#endif
#endif
#endif // LIBUSOCKETS_H

View File

@@ -0,0 +1,364 @@
/*
* Authored by Alex Hultman, 2018-2021.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "libusockets.h"
#include "internal/internal.h"
#include <stdlib.h>
#include <sys/ioctl.h>
/* The loop has 2 fallthrough polls */
void us_internal_loop_data_init(struct us_loop_t *loop, void (*wakeup_cb)(struct us_loop_t *loop),
void (*pre_cb)(struct us_loop_t *loop), void (*post_cb)(struct us_loop_t *loop)) {
loop->data.sweep_timer = us_create_timer(loop, 1, 0);
loop->data.recv_buf = malloc(LIBUS_RECV_BUFFER_LENGTH + LIBUS_RECV_BUFFER_PADDING * 2);
loop->data.ssl_data = 0;
loop->data.head = 0;
loop->data.iterator = 0;
loop->data.closed_head = 0;
loop->data.low_prio_head = 0;
loop->data.low_prio_budget = 0;
loop->data.pre_cb = pre_cb;
loop->data.post_cb = post_cb;
loop->data.iteration_nr = 0;
loop->data.wakeup_async = us_internal_create_async(loop, 1, 0);
us_internal_async_set(loop->data.wakeup_async, (void (*)(struct us_internal_async *)) wakeup_cb);
}
void us_internal_loop_data_free(struct us_loop_t *loop) {
#ifndef LIBUS_NO_SSL
us_internal_free_loop_ssl_data(loop);
#endif
free(loop->data.recv_buf);
us_timer_close(loop->data.sweep_timer);
us_internal_async_close(loop->data.wakeup_async);
}
void us_wakeup_loop(struct us_loop_t *loop) {
us_internal_async_wakeup(loop->data.wakeup_async);
}
void us_internal_loop_link(struct us_loop_t *loop, struct us_socket_context_t *context) {
/* Insert this context as the head of loop */
context->next = loop->data.head;
context->prev = 0;
if (loop->data.head) {
loop->data.head->prev = context;
}
loop->data.head = context;
}
/* Unlink is called before free */
void us_internal_loop_unlink(struct us_loop_t *loop, struct us_socket_context_t *context) {
if (loop->data.head == context) {
loop->data.head = context->next;
if (loop->data.head) {
loop->data.head->prev = 0;
}
} else {
context->prev->next = context->next;
if (context->next) {
context->next->prev = context->prev;
}
}
}
/* This functions should never run recursively */
void us_internal_timer_sweep(struct us_loop_t *loop) {
struct us_internal_loop_data_t *loop_data = &loop->data;
/* For all socket contexts in this loop */
for (loop_data->iterator = loop_data->head; loop_data->iterator; loop_data->iterator = loop_data->iterator->next) {
struct us_socket_context_t *context = loop_data->iterator;
/* Update this context's timestamps (this could be moved to loop and done once) */
context->global_tick++;
unsigned char short_ticks = context->timestamp = context->global_tick % 240;
unsigned char long_ticks = context->long_timestamp = (context->global_tick / 15) % 240;
/* Begin at head */
struct us_socket_t *s = context->head_sockets;
while (s) {
/* Seek until end or timeout found (tightest loop) */
while (1) {
/* We only read from 1 random cache line here */
if (short_ticks == s->timeout || long_ticks == s->long_timeout) {
break;
}
/* Did we reach the end without a find? */
if ((s = s->next) == 0) {
goto next_context;
}
}
/* Here we have a timeout to emit (slow path) */
context->iterator = s;
if (short_ticks == s->timeout) {
s->timeout = 255;
context->on_socket_timeout(s);
}
if (context->iterator == s && long_ticks == s->long_timeout) {
s->long_timeout = 255;
context->on_socket_long_timeout(s);
}
/* Check for unlink / link (if the event handler did not modify the chain, we step 1) */
if (s == context->iterator) {
s = s->next;
} else {
/* The iterator was changed by event handler */
s = context->iterator;
}
}
/* We always store a 0 to context->iterator here since we are no longer iterating this context */
next_context:
context->iterator = 0;
}
}
/* We do not want to block the loop with tons and tons of CPU-intensive work for SSL handshakes.
* Spread it out during many loop iterations, prioritizing already open connections, they are far
* easier on CPU */
static const int MAX_LOW_PRIO_SOCKETS_PER_LOOP_ITERATION = 5;
void us_internal_handle_low_priority_sockets(struct us_loop_t *loop) {
struct us_internal_loop_data_t *loop_data = &loop->data;
struct us_socket_t *s;
loop_data->low_prio_budget = MAX_LOW_PRIO_SOCKETS_PER_LOOP_ITERATION;
for (s = loop_data->low_prio_head; s && loop_data->low_prio_budget > 0; s = loop_data->low_prio_head, loop_data->low_prio_budget--) {
/* Unlink this socket from the low-priority queue */
loop_data->low_prio_head = s->next;
if (s->next) s->next->prev = 0;
s->next = 0;
us_internal_socket_context_link_socket(s->context, s);
us_poll_change(&s->p, us_socket_context(0, s)->loop, us_poll_events(&s->p) | LIBUS_SOCKET_READABLE);
s->low_prio_state = 2;
}
}
/* Note: Properly takes the linked list and timeout sweep into account */
void us_internal_free_closed_sockets(struct us_loop_t *loop) {
/* Free all closed sockets (maybe it is better to reverse order?) */
if (loop->data.closed_head) {
for (struct us_socket_t *s = loop->data.closed_head; s; ) {
struct us_socket_t *next = s->next;
us_poll_free((struct us_poll_t *) s, loop);
s = next;
}
loop->data.closed_head = 0;
}
}
void sweep_timer_cb(struct us_internal_callback_t *cb) {
us_internal_timer_sweep(cb->loop);
}
long long us_loop_iteration_number(struct us_loop_t *loop) {
return loop->data.iteration_nr;
}
/* These may have somewhat different meaning depending on the underlying event library */
void us_internal_loop_pre(struct us_loop_t *loop) {
loop->data.iteration_nr++;
us_internal_handle_low_priority_sockets(loop);
loop->data.pre_cb(loop);
}
void us_internal_loop_post(struct us_loop_t *loop) {
us_internal_free_closed_sockets(loop);
loop->data.post_cb(loop);
}
void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int events) {
switch (us_internal_poll_type(p)) {
case POLL_TYPE_CALLBACK: {
struct us_internal_callback_t *cb = (struct us_internal_callback_t *) p;
/* Timers, asyncs should accept (read), while UDP sockets should obviously not */
if (!cb->leave_poll_ready) {
/* Let's just have this macro to silence the CodeQL alert regarding empty function when using libuv */
#ifndef LIBUS_USE_LIBUV
us_internal_accept_poll_event(p);
#endif
}
cb->cb(cb->cb_expects_the_loop ? (struct us_internal_callback_t *) cb->loop : (struct us_internal_callback_t *) &cb->p);
}
break;
case POLL_TYPE_SEMI_SOCKET: {
/* Both connect and listen sockets are semi-sockets
* but they poll for different events */
if (us_poll_events(p) == LIBUS_SOCKET_WRITABLE) {
struct us_socket_t *s = (struct us_socket_t *) p;
/* It is perfectly possible to come here with an error */
if (error) {
/* Emit error, close without emitting on_close */
s->context->on_connect_error(s, 0);
us_socket_close_connecting(0, s);
} else {
/* All sockets poll for readable */
us_poll_change(p, s->context->loop, LIBUS_SOCKET_READABLE);
/* We always use nodelay */
bsd_socket_nodelay(us_poll_fd(p), 1);
/* We are now a proper socket */
us_internal_poll_set_type(p, POLL_TYPE_SOCKET);
/* If we used a connection timeout we have to reset it here */
us_socket_timeout(0, s, 0);
s->context->on_open(s, 1, 0, 0);
}
} else {
struct us_listen_socket_t *listen_socket = (struct us_listen_socket_t *) p;
struct bsd_addr_t addr;
LIBUS_SOCKET_DESCRIPTOR client_fd = bsd_accept_socket(us_poll_fd(p), &addr);
if (client_fd == LIBUS_SOCKET_ERROR) {
/* Todo: start timer here */
} else {
/* Todo: stop timer if any */
do {
struct us_poll_t *accepted_p = us_create_poll(us_socket_context(0, &listen_socket->s)->loop, 0, sizeof(struct us_socket_t) - sizeof(struct us_poll_t) + listen_socket->socket_ext_size);
us_poll_init(accepted_p, client_fd, POLL_TYPE_SOCKET);
us_poll_start(accepted_p, listen_socket->s.context->loop, LIBUS_SOCKET_READABLE);
struct us_socket_t *s = (struct us_socket_t *) accepted_p;
s->context = listen_socket->s.context;
s->timeout = 255;
s->long_timeout = 255;
s->low_prio_state = 0;
/* We always use nodelay */
bsd_socket_nodelay(client_fd, 1);
us_internal_socket_context_link_socket(listen_socket->s.context, s);
listen_socket->s.context->on_open(s, 0, bsd_addr_get_ip(&addr), bsd_addr_get_ip_length(&addr));
/* Exit accept loop if listen socket was closed in on_open handler */
if (us_socket_is_closed(0, &listen_socket->s)) {
break;
}
} while ((client_fd = bsd_accept_socket(us_poll_fd(p), &addr)) != LIBUS_SOCKET_ERROR);
}
}
}
break;
case POLL_TYPE_SOCKET_SHUT_DOWN:
case POLL_TYPE_SOCKET: {
/* We should only use s, no p after this point */
struct us_socket_t *s = (struct us_socket_t *) p;
if (events & LIBUS_SOCKET_WRITABLE && !error) {
/* Note: if we failed a write as a socket of one loop then adopted
* to another loop, this will be wrong. Absurd case though */
s->context->loop->data.last_write_failed = 0;
s = s->context->on_writable(s);
if (us_socket_is_closed(0, s)) {
return;
}
/* If we have no failed write or if we shut down, then stop polling for more writable */
if (!s->context->loop->data.last_write_failed || us_socket_is_shut_down(0, s)) {
us_poll_change(&s->p, us_socket_context(0, s)->loop, us_poll_events(&s->p) & LIBUS_SOCKET_READABLE);
}
}
if (events & LIBUS_SOCKET_READABLE) {
/* Contexts may prioritize down sockets that are currently readable, e.g. when SSL handshake has to be done.
* SSL handshakes are CPU intensive, so we limit the number of handshakes per loop iteration, and move the rest
* to the low-priority queue */
if (s->context->is_low_prio(s)) {
if (s->low_prio_state == 2) {
s->low_prio_state = 0; /* Socket has been delayed and now it's time to process incoming data for one iteration */
} else if (s->context->loop->data.low_prio_budget > 0) {
s->context->loop->data.low_prio_budget--; /* Still having budget for this iteration - do normal processing */
} else {
us_poll_change(&s->p, us_socket_context(0, s)->loop, us_poll_events(&s->p) & LIBUS_SOCKET_WRITABLE);
us_internal_socket_context_unlink_socket(s->context, s);
/* Link this socket to the low-priority queue - we use a LIFO queue, to prioritize newer clients that are
* maybe not already timeouted - sounds unfair, but works better in real-life with smaller client-timeouts
* under high load */
s->prev = 0;
s->next = s->context->loop->data.low_prio_head;
if (s->next) s->next->prev = s;
s->context->loop->data.low_prio_head = s;
s->low_prio_state = 1;
break;
}
}
int length = bsd_recv(us_poll_fd(&s->p), s->context->loop->data.recv_buf + LIBUS_RECV_BUFFER_PADDING, LIBUS_RECV_BUFFER_LENGTH, 0);
if (length > 0) {
s = s->context->on_data(s, s->context->loop->data.recv_buf + LIBUS_RECV_BUFFER_PADDING, length);
} else if (!length) {
if (us_socket_is_shut_down(0, s)) {
/* We got FIN back after sending it */
/* Todo: We should give "CLEAN SHUTDOWN" as reason here */
s = us_socket_close(0, s, 0, NULL);
} else {
/* We got FIN, so stop polling for readable */
us_poll_change(&s->p, us_socket_context(0, s)->loop, us_poll_events(&s->p) & LIBUS_SOCKET_WRITABLE);
s = s->context->on_end(s);
}
} else if (length == LIBUS_SOCKET_ERROR && !bsd_would_block()) {
/* Todo: decide also here what kind of reason we should give */
s = us_socket_close(0, s, 0, NULL);
return;
}
}
/* Such as epollerr epollhup */
if (error) {
/* Todo: decide what code we give here */
s = us_socket_close(0, s, 0, NULL);
return;
}
}
break;
}
}
/* Integration only requires the timer to be set up */
void us_loop_integrate(struct us_loop_t *loop) {
us_timer_set(loop->data.sweep_timer, (void (*)(struct us_timer_t *)) sweep_timer_cb, LIBUS_TIMEOUT_GRANULARITY * 1000, LIBUS_TIMEOUT_GRANULARITY * 1000);
}
void *us_loop_ext(struct us_loop_t *loop) {
return loop + 1;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
#ifdef LIBUS_USE_QUIC
#ifndef LIBUS_QUIC_H
#define LIBUS_QUIC_H
/* Experimental QUIC functions */
#include "libusockets.h"
typedef struct {
const char *cert_file_name;
const char *key_file_name;
const char *passphrase;
} us_quic_socket_context_options_t;
typedef struct {
/* Refers to either the shared listen socket or the client UDP socket */
void *udp_socket;
} us_quic_socket_t;
struct us_quic_socket_context_s;
struct us_quic_listen_socket_s;
struct us_quic_stream_s;
typedef struct us_quic_socket_context_s us_quic_socket_context_t;
typedef struct us_quic_listen_socket_s us_quic_listen_socket_t;
typedef struct us_quic_stream_s us_quic_stream_t;
void *us_quic_stream_ext(us_quic_stream_t *s);
int us_quic_stream_write(us_quic_stream_t *s, char *data, int length);
int us_quic_stream_shutdown(us_quic_stream_t *s);
int us_quic_stream_shutdown_read(us_quic_stream_t *s);
void us_quic_stream_close(us_quic_stream_t *s);
int us_quic_socket_context_get_header(us_quic_socket_context_t *context, int index, char **name, int *name_length, char **value, int *value_length);
void us_quic_socket_context_set_header(us_quic_socket_context_t *context, int index, const char *key, int key_length, const char *value, int value_length);
void us_quic_socket_context_send_headers(us_quic_socket_context_t *context, us_quic_stream_t *s, int num, int has_body);
us_quic_socket_context_t *us_create_quic_socket_context(struct us_loop_t *loop, us_quic_socket_context_options_t options, int ext_size);
us_quic_listen_socket_t *us_quic_socket_context_listen(us_quic_socket_context_t *context, const char *host, int port, int ext_size);
us_quic_socket_t *us_quic_socket_context_connect(us_quic_socket_context_t *context, const char *host, int port, int ext_size);
void us_quic_socket_create_stream(us_quic_socket_t *s, int ext_size);
us_quic_socket_t *us_quic_stream_socket(us_quic_stream_t *s);
/* This one is ugly and is only used to make clean examples */
int us_quic_stream_is_client(us_quic_stream_t *s);
void us_quic_socket_context_on_stream_data(us_quic_socket_context_t *context, void(*on_stream_data)(us_quic_stream_t *s, char *data, int length));
void us_quic_socket_context_on_stream_end(us_quic_socket_context_t *context, void(*on_stream_data)(us_quic_stream_t *s));
void us_quic_socket_context_on_stream_headers(us_quic_socket_context_t *context, void(*on_stream_headers)(us_quic_stream_t *s));
void us_quic_socket_context_on_stream_open(us_quic_socket_context_t *context, void(*on_stream_open)(us_quic_stream_t *s, int is_client));
void us_quic_socket_context_on_stream_close(us_quic_socket_context_t *context, void(*on_stream_close)(us_quic_stream_t *s));
void us_quic_socket_context_on_open(us_quic_socket_context_t *context, void(*on_open)(us_quic_socket_t *s, int is_client));
void us_quic_socket_context_on_close(us_quic_socket_context_t *context, void(*on_close)(us_quic_socket_t *s));
void us_quic_socket_context_on_stream_writable(us_quic_socket_context_t *context, void(*on_stream_writable)(us_quic_stream_t *s));
void *us_quic_socket_context_ext(us_quic_socket_context_t *context);
us_quic_socket_context_t *us_quic_socket_context(us_quic_socket_t *s);
#endif
#endif

View File

@@ -0,0 +1,295 @@
/*
* Authored by Alex Hultman, 2018-2021.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "libusockets.h"
#include "internal/internal.h"
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
/* Shared with SSL */
int us_socket_local_port(int ssl, struct us_socket_t *s) {
struct bsd_addr_t addr;
if (bsd_local_addr(us_poll_fd(&s->p), &addr)) {
return -1;
} else {
return bsd_addr_get_port(&addr);
}
}
void us_socket_shutdown_read(int ssl, struct us_socket_t *s) {
/* This syscall is idempotent so no extra check is needed */
bsd_shutdown_socket_read(us_poll_fd((struct us_poll_t *) s));
}
void us_socket_remote_address(int ssl, struct us_socket_t *s, char *buf, int *length) {
struct bsd_addr_t addr;
if (bsd_remote_addr(us_poll_fd(&s->p), &addr) || *length < bsd_addr_get_ip_length(&addr)) {
*length = 0;
} else {
*length = bsd_addr_get_ip_length(&addr);
memcpy(buf, bsd_addr_get_ip(&addr), *length);
}
}
struct us_socket_context_t *us_socket_context(int ssl, struct us_socket_t *s) {
return s->context;
}
void us_socket_timeout(int ssl, struct us_socket_t *s, unsigned int seconds) {
if (seconds) {
s->timeout = ((unsigned int)s->context->timestamp + ((seconds + 3) >> 2)) % 240;
} else {
s->timeout = 255;
}
}
void us_socket_long_timeout(int ssl, struct us_socket_t *s, unsigned int minutes) {
if (minutes) {
s->long_timeout = ((unsigned int)s->context->long_timestamp + minutes) % 240;
} else {
s->long_timeout = 255;
}
}
void us_socket_flush(int ssl, struct us_socket_t *s) {
if (!us_socket_is_shut_down(0, s)) {
bsd_socket_flush(us_poll_fd((struct us_poll_t *) s));
}
}
int us_socket_is_closed(int ssl, struct us_socket_t *s) {
return s->prev == (struct us_socket_t *) s->context;
}
int us_socket_is_established(int ssl, struct us_socket_t *s) {
/* Everything that is not POLL_TYPE_SEMI_SOCKET is established */
return us_internal_poll_type((struct us_poll_t *) s) != POLL_TYPE_SEMI_SOCKET;
}
/* Exactly the same as us_socket_close but does not emit on_close event */
struct us_socket_t *us_socket_close_connecting(int ssl, struct us_socket_t *s) {
if (!us_socket_is_closed(0, s)) {
us_internal_socket_context_unlink_socket(s->context, s);
us_poll_stop((struct us_poll_t *) s, s->context->loop);
bsd_close_socket(us_poll_fd((struct us_poll_t *) s));
/* Link this socket to the close-list and let it be deleted after this iteration */
s->next = s->context->loop->data.closed_head;
s->context->loop->data.closed_head = s;
/* Any socket with prev = context is marked as closed */
s->prev = (struct us_socket_t *) s->context;
//return s->context->on_close(s, code, reason);
}
return s;
}
/* Same as above but emits on_close */
struct us_socket_t *us_socket_close(int ssl, struct us_socket_t *s, int code, void *reason) {
if (!us_socket_is_closed(0, s)) {
if (s->low_prio_state == 1) {
/* Unlink this socket from the low-priority queue */
if (!s->prev) s->context->loop->data.low_prio_head = s->next;
else s->prev->next = s->next;
if (s->next) s->next->prev = s->prev;
s->prev = 0;
s->next = 0;
s->low_prio_state = 0;
} else {
us_internal_socket_context_unlink_socket(s->context, s);
}
#ifdef LIBUS_USE_KQUEUE
// kqueue automatically removes the fd from the set on close
// we can skip the system call for that case
us_internal_loop_update_pending_ready_polls(s->context->loop, (struct us_poll_t *)s, 0, us_poll_events((struct us_poll_t*)s), 0);
#else
/* Disable any instance of us in the pending ready poll list */
us_poll_stop((struct us_poll_t *) s, s->context->loop);
#endif
bsd_close_socket(us_poll_fd((struct us_poll_t *) s));
/* Link this socket to the close-list and let it be deleted after this iteration */
s->next = s->context->loop->data.closed_head;
s->context->loop->data.closed_head = s;
/* Any socket with prev = context is marked as closed */
s->prev = (struct us_socket_t *) s->context;
return s->context->on_close(s, code, reason);
}
return s;
}
// This function is the same as us_socket_close but:
// - does not emit on_close event
// - does not close
struct us_socket_t *us_socket_detach(int ssl, struct us_socket_t *s) {
if (!us_socket_is_closed(0, s)) {
if (s->low_prio_state == 1) {
/* Unlink this socket from the low-priority queue */
if (!s->prev) s->context->loop->data.low_prio_head = s->next;
else s->prev->next = s->next;
if (s->next) s->next->prev = s->prev;
s->prev = 0;
s->next = 0;
s->low_prio_state = 0;
} else {
us_internal_socket_context_unlink(s->context, s);
}
us_poll_stop((struct us_poll_t *) s, s->context->loop);
/* Link this socket to the close-list and let it be deleted after this iteration */
s->next = s->context->loop->data.closed_head;
s->context->loop->data.closed_head = s;
/* Any socket with prev = context is marked as closed */
s->prev = (struct us_socket_t *) s->context;
return s;
}
return s;
}
// This function is used for moving a socket between two different event loops
struct us_socket_t *us_socket_attach(int ssl, LIBUS_SOCKET_DESCRIPTOR client_fd, struct us_socket_context_t *ctx, int flags, int socket_ext_size) {
struct us_poll_t *accepted_p = us_create_poll(ctx->loop, 0, sizeof(struct us_socket_t) - sizeof(struct us_poll_t) + socket_ext_size);
us_poll_init(accepted_p, client_fd, POLL_TYPE_SOCKET);
us_poll_start(accepted_p, ctx->loop, flags);
struct us_socket_t *s = (struct us_socket_t *) accepted_p;
s->context = ctx;
s->timeout = 0;
s->low_prio_state = 0;
/* We always use nodelay */
bsd_socket_nodelay(client_fd, 1);
us_internal_socket_context_link(ctx, s);
if (ctx->on_open) ctx->on_open(s, 0, 0, 0);
return s;
}
/* Not shared with SSL */
void *us_socket_get_native_handle(int ssl, struct us_socket_t *s) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_get_native_handle((struct us_internal_ssl_socket_t *) s);
}
#endif
return (void *) (uintptr_t) us_poll_fd((struct us_poll_t *) s);
}
int us_socket_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_write((struct us_internal_ssl_socket_t *) s, data, length, msg_more);
}
#endif
if (us_socket_is_closed(ssl, s) || us_socket_is_shut_down(ssl, s)) {
return 0;
}
int written = bsd_send(us_poll_fd(&s->p), data, length, msg_more);
if (written != length) {
s->context->loop->data.last_write_failed = 1;
us_poll_change(&s->p, s->context->loop, LIBUS_SOCKET_READABLE | LIBUS_SOCKET_WRITABLE);
}
return written < 0 ? 0 : written;
}
void *us_socket_ext(int ssl, struct us_socket_t *s) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_ext((struct us_internal_ssl_socket_t *) s);
}
#endif
return s + 1;
}
int us_socket_is_shut_down(int ssl, struct us_socket_t *s) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_is_shut_down((struct us_internal_ssl_socket_t *) s);
}
#endif
return us_internal_poll_type(&s->p) == POLL_TYPE_SOCKET_SHUT_DOWN;
}
void us_socket_shutdown(int ssl, struct us_socket_t *s) {
#ifndef LIBUS_NO_SSL
if (ssl) {
us_internal_ssl_socket_shutdown((struct us_internal_ssl_socket_t *) s);
return;
}
#endif
/* Todo: should we emit on_close if calling shutdown on an already half-closed socket?
* We need more states in that case, we need to track RECEIVED_FIN
* so far, the app has to track this and call close as needed */
if (!us_socket_is_closed(ssl, s) && !us_socket_is_shut_down(ssl, s)) {
us_internal_poll_set_type(&s->p, POLL_TYPE_SOCKET_SHUT_DOWN);
us_poll_change(&s->p, s->context->loop, us_poll_events(&s->p) & LIBUS_SOCKET_READABLE);
bsd_shutdown_socket(us_poll_fd((struct us_poll_t *) s));
}
}
/*
Note: this assumes that the socket is non-TLS and will be adopted and wrapped with a new TLS context
context ext will not be copied to the new context, new context will contain us_wrapped_socket_context_t on ext
*/
struct us_socket_t *us_socket_wrap_with_tls(int ssl, struct us_socket_t *s, struct us_bun_socket_context_options_t options, struct us_socket_events_t events, int socket_ext_size) {
// only accepts non-TLS sockets
if (ssl) {
return NULL;
}
return(struct us_socket_t *) us_internal_ssl_socket_wrap_with_tls(s, options, events, socket_ext_size);
}
// if a TLS socket calls this, it will start SSL call open event and TLS handshake if required
// will have no effect if the socket is closed or is not TLS
struct us_socket_t* us_socket_open(int ssl, struct us_socket_t * s, int is_client, char* ip, int ip_length) {
if (ssl) {
return(struct us_socket_t *) us_internal_ssl_socket_open((struct us_internal_ssl_socket_t *)s, is_client, ip, ip_length);
}
return s;
}
int us_socket_raw_write(int ssl, struct us_socket_t *s, const char *data, int length, int msg_more) {
#ifndef LIBUS_NO_SSL
if (ssl) {
return us_internal_ssl_socket_raw_write((struct us_internal_ssl_socket_t *) s, data, length, msg_more);
}
#endif
// non-TLS is always raw
return us_socket_write(ssl, s, data, length, msg_more);
}

View File

@@ -0,0 +1,147 @@
/*
* Authored by Alex Hultman, 2018-2021.
* Intellectual property of third-party.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "libusockets.h"
#include "internal/internal.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int us_udp_packet_buffer_ecn(struct us_udp_packet_buffer_t *buf, int index) {
return bsd_udp_packet_buffer_ecn(buf, index);
}
int us_udp_packet_buffer_local_ip(struct us_udp_packet_buffer_t *buf, int index, char *ip) {
return bsd_udp_packet_buffer_local_ip(buf, index, ip);
}
char *us_udp_packet_buffer_peer(struct us_udp_packet_buffer_t *buf, int index) {
return bsd_udp_packet_buffer_peer(buf, index);
}
char *us_udp_packet_buffer_payload(struct us_udp_packet_buffer_t *buf, int index) {
return bsd_udp_packet_buffer_payload(buf, index);
}
int us_udp_packet_buffer_payload_length(struct us_udp_packet_buffer_t *buf, int index) {
return bsd_udp_packet_buffer_payload_length(buf, index);
}
// what should we return? number of sent datagrams?
int us_udp_socket_send(struct us_udp_socket_t *s, struct us_udp_packet_buffer_t *buf, int num) {
int fd = us_poll_fd((struct us_poll_t *) s);
// we need to poll out if we failed
return bsd_sendmmsg(fd, buf, num, 0);
}
int us_udp_socket_receive(struct us_udp_socket_t *s, struct us_udp_packet_buffer_t *buf) {
int fd = us_poll_fd((struct us_poll_t *) s);
return bsd_recvmmsg(fd, buf, LIBUS_UDP_MAX_NUM, 0, 0);
}
void us_udp_buffer_set_packet_payload(struct us_udp_packet_buffer_t *send_buf, int index, int offset, void *payload, int length, void *peer_addr) {
bsd_udp_buffer_set_packet_payload(send_buf, index, offset, payload, length, peer_addr);
}
struct us_udp_packet_buffer_t *us_create_udp_packet_buffer() {
return (struct us_udp_packet_buffer_t *) bsd_create_udp_packet_buffer();
}
struct us_internal_udp_t {
struct us_internal_callback_t cb;
struct us_udp_packet_buffer_t *receive_buf;
void (*data_cb)(struct us_udp_socket_t *, struct us_udp_packet_buffer_t *, int);
void (*drain_cb)(struct us_udp_socket_t *);
void *user;
/* An UDP socket can only ever be bound to one single port regardless of how
* many interfaces it may listen to. Therefore we cache the port after creation
* and use it to build a proper and full sockaddr_in or sockaddr_in6 for every received packet */
int port;
};
int us_udp_socket_bound_port(struct us_udp_socket_t *s) {
return ((struct us_internal_udp_t *) s)->port;
}
/* Internal wrapper, move from here */
void internal_on_udp_read(struct us_udp_socket_t *s) {
// lookup receive buffer and callback here
struct us_internal_udp_t *udp = (struct us_internal_udp_t *) s;
int packets = us_udp_socket_receive(s, udp->receive_buf);
//printf("Packets: %d\n", packets);
// we need to get the socket data and lookup its callback here
udp->data_cb(s, udp->receive_buf, packets);
}
void *us_udp_socket_user(struct us_udp_socket_t *s) {
struct us_internal_udp_t *udp = (struct us_internal_udp_t *) s;
return udp->user;
}
struct us_udp_socket_t *us_create_udp_socket(struct us_loop_t *loop, struct us_udp_packet_buffer_t *buf, void (*data_cb)(struct us_udp_socket_t *, struct us_udp_packet_buffer_t *, int), void (*drain_cb)(struct us_udp_socket_t *), const char *host, unsigned short port, void *user) {
LIBUS_SOCKET_DESCRIPTOR fd = bsd_create_udp_socket(host, port);
if (fd == LIBUS_SOCKET_ERROR) {
return 0;
}
/* If buf is 0 then create one here */
if (!buf) {
buf = us_create_udp_packet_buffer();
}
int ext_size = 0;
int fallthrough = 0;
struct us_poll_t *p = us_create_poll(loop, fallthrough, sizeof(struct us_internal_udp_t) + ext_size);
us_poll_init(p, fd, POLL_TYPE_CALLBACK);
struct us_internal_udp_t *cb = (struct us_internal_udp_t *) p;
cb->cb.loop = loop;
cb->cb.cb_expects_the_loop = 0;
cb->cb.leave_poll_ready = 1;
/* Get and store the port once */
struct bsd_addr_t tmp;
bsd_local_addr(fd, &tmp);
cb->port = bsd_addr_get_port(&tmp);
printf("The port of UDP is: %d\n", cb->port);
/* There is no udp socket context, only user data */
/* This should really be ext like everything else */
cb->user = user;
cb->data_cb = data_cb;
cb->receive_buf = buf;
cb->drain_cb = drain_cb;
cb->cb.cb = (void (*)(struct us_internal_callback_t *)) internal_on_udp_read;
us_poll_start((struct us_poll_t *) cb, cb->cb.loop, LIBUS_SOCKET_READABLE);
return (struct us_udp_socket_t *) cb;
}

View File

@@ -0,0 +1,57 @@
extern "C" {
void *sni_new();
void sni_free(void *sni, void (*cb)(void *user));
int sni_add(void *sni, const char *hostname, void *user);
void *sni_remove(void *sni, const char *hostname);
void *sni_find(void *sni, const char *hostname);
}
#include <assert.h>
#include <stdio.h>
/* Todo: replace 13, 14 and 15 with malloc */
//void *WILDCARD_GOOGLE_COM = strdup("*.google.com");
//void *TEST_GOOGLE_COM = strdup("test.google.com");
void sni_free_cb(void *user) {
printf("Freeing %p\n", user);
}
int main() {
void *sni = sni_new();
/* Adding should succeed */
assert(sni_add(sni, "*.google.com", 13) == 0);
assert(sni_add(sni, "test.google.com", 14) == 0);
/* Adding the same name should not overwrite existing */
assert(sni_add(sni, "*.google.com", 15) != 0);
assert(sni_find(sni, "anything.google.com") == 13);
assert(sni_find(sni, "docs.google.com") == 13);
assert(sni_find(sni, "*.google.com") == 13);
assert(sni_find(sni, "test.google.com") == 14);
assert(sni_find(sni, "yolo.nothing.com") == 0);
assert(sni_find(sni, "yolo.google.com") == 13);
/* Removing should work */
assert(sni_remove(sni, "test.google.com") == 14);
assert(sni_find(sni, "test.google.com") == 13);
assert(sni_remove(sni, "*.google.com") == 13);
assert(sni_find(sni, "test.google.com") == 0);
/* Removing parent with data should not remove child with data */
assert(sni_add(sni, "www.google.com", 16) == 0);
assert(sni_add(sni, "www.google.com.au.ck.uk", 17) == 0);
assert(sni_find(sni, "www.google.com") == 16);
assert(sni_find(sni, "www.google.com.au.ck.uk") == 17);
assert(sni_remove(sni, "www.google.com.yolo") == 0);
assert(sni_remove(sni, "www.google.com.au.ck.uk") == 17);
assert(sni_find(sni, "www.google.com") == 16);
/* Free should not leave anything remaining (test with ASAN leaksanitizer) */
sni_free(sni, sni_free_cb);
printf("OK\n");
}

View File

@@ -0,0 +1,67 @@
name: "CodeQL"
on:
push:
branches: [ 'master' ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ 'master' ]
schedule:
- cron: '0 17 * * 2'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'cpp', 'javascript' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
queries: +security-and-quality
- run: |
echo "Fetch submodules"
git submodule update --init --recursive
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"

View File

@@ -0,0 +1,92 @@
name: C++ CI
on: [push]
jobs:
short_fuzzing:
runs-on: ubuntu-latest
steps:
- name: Build fuzzers
id: build
uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
with:
oss-fuzz-project-name: 'uwebsockets'
language: c++
- name: Run fuzzers
uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
with:
oss-fuzz-project-name: 'uwebsockets'
language: c++
fuzz-seconds: 600
- name: Upload crash
uses: actions/upload-artifact@v3
if: failure() && steps.build.outcome == 'success'
with:
name: artifacts
path: ./out/artifacts
build_windows:
runs-on: windows-latest
steps:
- name: Clone source
run: git clone --recursive https://github.com/uNetworking/uWebSockets.git
- name: Install libuv
run: |
vcpkg install libuv:x64-windows
cp C:\vcpkg\installed\x64-windows\bin\uv.dll uWebSockets\uv.dll
- uses: ilammy/msvc-dev-cmd@v1
- name: Build examples
run: |
cd uWebSockets
$Env:WITH_ZLIB='0'; $ENV:WITH_LTO='0'; $Env:CC='clang';
$ENV:CFLAGS='-I C:\vcpkg\installed\x64-windows\include';
$ENV:LDFLAGS='-L C:\vcpkg\installed\x64-windows\lib';
$ENV:CXX='clang++'; $ENV:EXEC_SUFFIX='.exe'; $ENV:WITH_LIBUV='1'; nmake
ls
- name: Run unit tests
run: $Env:CC='clang'; $Env:CXX='clang++'; make -C uWebSockets/tests
- name: Run smoke test
run: |
cd uWebSockets
iwr https://deno.land/x/install/install.ps1 -useb | iex
Start-Process -NoNewWindow .\Crc32
sleep 1
deno run --allow-net tests\smoke.mjs
Stop-Process -Name Crc32
build_linux:
runs-on: ubuntu-latest
steps:
- name: Clone source
run: git clone --recursive https://github.com/uNetworking/uWebSockets.git
- name: Build source
run: make -C uWebSockets
- name: List binaries
run: ls uWebSockets
- name: Install Deno
run: curl -fsSL https://deno.land/x/install/install.sh | sh
- name: Run smoke test
run: make -C uWebSockets/tests smoke
- name: Run unit tests
run: make -C uWebSockets/tests
build_osx:
runs-on: macos-latest
steps:
- name: Clone source
run: git clone --recursive https://github.com/uNetworking/uWebSockets.git
- name: Build source
run: make -C uWebSockets
- name: List binaries
run: ls uWebSockets
- name: Install Deno
run: curl -fsSL https://deno.land/x/install/install.sh | sh
- name: Run smoke test
run: make -C uWebSockets/tests smoke
- name: Run unit tests
run: make -C uWebSockets/tests

81
packages/bun-uws/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,81 @@
{
"files.associations": {
"*.lock": "yarnlock",
"socket.h": "c",
"in.h": "c",
"string.h": "c",
"tcp.h": "c",
"vector": "cpp",
"string_view": "cpp",
"array": "cpp",
"future": "cpp",
"istream": "cpp",
"locale": "cpp",
"memory": "cpp",
"thread": "cpp",
"tuple": "cpp",
"variant": "cpp",
"__bit_reference": "cpp",
"__bits": "cpp",
"__config": "cpp",
"__debug": "cpp",
"__errc": "cpp",
"__hash_table": "cpp",
"__locale": "cpp",
"__mutex_base": "cpp",
"__node_handle": "cpp",
"__split_buffer": "cpp",
"__threading_support": "cpp",
"__tree": "cpp",
"__tuple": "cpp",
"any": "cpp",
"atomic": "cpp",
"bit": "cpp",
"bitset": "cpp",
"cctype": "cpp",
"charconv": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"complex": "cpp",
"condition_variable": "cpp",
"cstdarg": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"exception": "cpp",
"forward_list": "cpp",
"fstream": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"ios": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"limits": "cpp",
"list": "cpp",
"map": "cpp",
"mutex": "cpp",
"new": "cpp",
"optional": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"set": "cpp",
"sstream": "cpp",
"stack": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string": "cpp",
"system_error": "cpp",
"type_traits": "cpp",
"typeinfo": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"__verbose_abort": "cpp",
"algorithm": "cpp"
}
}

View File

@@ -0,0 +1,24 @@
# This is the GNU Make shim for Linux and macOS
DESTDIR ?=
prefix ?= /usr/local
examples: default
./build examples
clean: default
./build clean
capi: default
./build capi
install:
mkdir -p "$(DESTDIR)$(prefix)/include/uWebSockets"
cp -r src/* "$(DESTDIR)$(prefix)/include/uWebSockets"
all: default
./build all
default:
$(MAKE) -C uSockets
$(CC) build.c -o build

201
packages/bun-uws/LICENSE Normal file
View File

@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,3 @@
## Bun's fork of uWebSockets
Bun's HTTP & WebSocket server is based on [uWebSockets](https://github.com/uNetworking/uWebSockets). Thanks to @uNetworkingAB for the great work!

View File

@@ -0,0 +1,7 @@
default:
clang -flto -O3 -DLIBUS_USE_OPENSSL -I../uSockets/src ../uSockets/src/*.c ../uSockets/src/eventing/*.c ../uSockets/src/crypto/*.c broadcast_test.c load_test.c scale_test.c -c
clang++ -flto -O3 -DLIBUS_USE_OPENSSL -I../uSockets/src ../uSockets/src/crypto/*.cpp -c -std=c++17
clang++ -flto -O3 -DLIBUS_USE_OPENSSL `ls *.o | grep -Ev "load_test|scale_test"` -lssl -lcrypto -o broadcast_test
clang++ -flto -O3 -DLIBUS_USE_OPENSSL `ls *.o | grep -Ev "broadcast_test|scale_test"` -lssl -lcrypto -o load_test
clang++ -flto -O3 -DLIBUS_USE_OPENSSL `ls *.o | grep -Ev "broadcast_test|load_test"` -lssl -lcrypto -o scale_test

View File

@@ -0,0 +1,36 @@
# Benchmark-driven development
Making decisions based on scientific benchmarking **while** you develop can guide you to create very efficient solutions if you have the dicipline to follow through. µWebSockets performs with **98%** the theoretical maximum for any user space Linux process - if anything would ever be faster, it would only be so by less than 2%. We know of no such project.
Http | WebSockets
--- | ---
![](../misc/bigshot_lineup.png) | ![](../misc/websocket_lineup.png)
Because of the big lead in cleartext performance, it's actually possible to enable TLS 1.3 encryption in µWebSockets and still beat most of the competition in an unfair cleartext-vs-encrypted run. Performance retention of TLS 1.3 encryption with µWebSockets is about 60%, so you do the math.
All of this is possible thanks to extensive benchmarking of many discarded prototypes & designs during development. The very first thing done in this project was to benchmark the Linux kernel against itself, to get a clear idea of expected maximum performance and thus a performance budget on this platform.
From that point every line of code was benchmarked against the budget and thrown away if it failed the vision. Today µWebSockets does WebSocket messaging without any significant overhead, making it very unlikely to ever be outperformed.
Of course, memory usage has always been a big factor in this. The name µWebSockets is meant to signify "small WebSockets" and comes from the memory optimizations made throughout. Holding many WebSockets should not require lots of RAM.
If you're looking for a performant solution, look no further.
## Common benchmarking mistakes
It is very common, extremely common in fact, that people try and benchmark µWebSockets using a scripted Node.js client such as autocannon, ws, or anything similar. It might seem like an okay method but it really isn't. µWebSockets is 12x faster than Node.js, so trying to stress µWebSockets using Node.js is almost impossible. Maybe if you have a 16-core CPU and dedicate 15 cores to Node.js and 1 core to µWebSockets.
So whatever you do, it is of greatest importance that you actually **do check and make sure that µWebSockets is being stressed to 100% CPU-time** before noting the result. If it isn't, then you're not really benchmarking µWebSockets - you're benchmarking your client, trying to stress µWebSockets! Please don't make this mistake.
## Why "hello world" benchmarking?
Contrary to popular belief, "hello world benchmarks" are the most accurate and realistic gauges of performance for the kind of applications µWebSockets is designed for:
* IO-gaming (latency)
* Signalling (memory overhead)
* Trading (latency)
* Finance (latency)
* Chatting (memory overhead)
* Notifications (memory overhead)
Most business applications of the above mentioned categories are implemented without a central on-disk DB, blocking or severely limiting hot-path performance. As such, web IO becomes a significant part of overall bottleneck, if not the only bottleneck. Message echoing of around 1-16 kB or even as small as 512 bytes is a good test of the overall server plumbing (receive -> timeout clear -> emit to app -> timeout set -> send) for these applications.
Of course, if you build an app that *absolutely must* have an on-disk SQL DB central to all hot-paths, then µWebSockets is not the right tool for your app. Keep in mind that, finding a case where µWebSockets makes no difference, does not mean µWebSockets never makes a difference.

View File

@@ -0,0 +1,196 @@
/* This benchmark establishes _connections_ number of WebSocket
clients, then iteratively performs the following:
1. Send one message for every client.
2. Wait for the quadratic (_connections_^2) amount of responses from the server.
3. Once received all expected bytes, repeat by going to step 1.
Every 4 seconds we print the current average "iterations per second".
*/
#include <libusockets.h>
int SSL;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
unsigned char web_socket_request[26] = {130, 128 | 20, 1, 2, 3, 4};
char request[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
"Host: server.example.com\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
char *host;
int port;
int connections;
int satisfied_sockets;
int iterations;
struct http_socket {
/* How far we have streamed our websocket request */
int offset;
/* How far we have streamed our upgrade request */
int upgrade_offset;
/* Are we upgraded? */
int is_upgraded;
/* Bytes received */
int bytes_received;
};
/* We track upgraded websockets */
void **web_sockets;
int num_web_sockets;
/* We don't need any of these */
void noop(struct us_loop_t *loop) {
}
void start_iteration() {
for (int i = 0; i < num_web_sockets; i++) {
struct us_socket_t *s = (struct us_socket_t *) web_sockets[i];
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, sizeof(web_socket_request), 0);
}
}
void next_connection(struct us_socket_t *s) {
/* Add this connection to our array */
web_sockets[num_web_sockets++] = s;
/* We could wait with this until properly upgraded */
if (--connections) {
us_socket_context_connect(SSL, us_socket_context(SSL, s), host, port, NULL, 0, sizeof(struct http_socket));
} else {
printf("Running benchmark now...\n");
start_iteration();
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
}
}
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Are we still not upgraded yet? */
if (http_socket->upgrade_offset < sizeof(request) - 1) {
http_socket->upgrade_offset += us_socket_write(SSL, s, request + http_socket->upgrade_offset, sizeof(request) - 1 - http_socket->upgrade_offset, 0);
} else {
/* Stream whatever is remaining of the request */
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, sizeof(web_socket_request) - http_socket->offset, 0);
}
return s;
}
struct us_socket_t *on_http_socket_close(struct us_socket_t *s, int code, void *reason) {
printf("Client was disconnected, exiting!\n");
exit(-1);
return s;
}
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
return us_socket_close(SSL, s, 0, NULL);
}
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
/* Get socket extension and the socket's context's extension */
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Are we already upgraded? */
if (http_socket->is_upgraded) {
http_socket->bytes_received += length;
if (http_socket->bytes_received == (sizeof(web_socket_request) - 4) * num_web_sockets) {
satisfied_sockets++;
http_socket->bytes_received = 0;
if (satisfied_sockets == num_web_sockets) {
iterations++;
satisfied_sockets = 0;
start_iteration();
}
}
} else {
/* We assume the server is not sending anything immediately following upgrade and that we get rnrn in one chunk */
if (length >= 4 && data[length - 1] == '\n' && data[length - 2] == '\r' && data[length - 3] == '\n' && data[length - 4] == '\r') {
http_socket->is_upgraded = 1;
next_connection(s);
}
}
return s;
}
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Reset offsets */
http_socket->offset = 0;
http_socket->is_upgraded = 0;
http_socket->bytes_received = 0;
/* Send an upgrade request */
http_socket->upgrade_offset = us_socket_write(SSL, s, request, sizeof(request) - 1, 0);
return s;
}
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
/* Print current statistics */
printf("Iterations/second (%d clients): %f\n", num_web_sockets, ((float)iterations) / LIBUS_TIMEOUT_GRANULARITY);
iterations = 0;
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
return s;
}
int main(int argc, char **argv) {
/* Parse host and port */
if (argc != 5) {
printf("Usage: connections host port ssl\n");
return 0;
}
port = atoi(argv[3]);
host = malloc(strlen(argv[2]) + 1);
memcpy(host, argv[2], strlen(argv[2]) + 1);
connections = atoi(argv[1]);
SSL = atoi(argv[4]);
/* Allocate room for every socket */
web_sockets = (void **) malloc(sizeof(void *) * connections);
/* Create the event loop */
struct us_loop_t *loop = us_create_loop(0, noop, noop, noop, 0);
/* Create a socket context for HTTP */
struct us_socket_context_options_t options = {};
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
/* Set up event handlers */
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
/* Start making HTTP connections */
us_socket_context_connect(SSL, http_context, host, port, NULL, 0, sizeof(struct http_socket));
us_loop_run(loop);
}

View File

@@ -0,0 +1,307 @@
/* This is a simple yet efficient WebSocket server benchmark much like WRK */
#define _BSD_SOURCE
#ifdef __APPLE__
#include <libkern/OSByteOrder.h>
#define htobe16(x) OSSwapHostToBigInt16(x)
#define htole16(x) OSSwapHostToLittleInt16(x)
#define be16toh(x) OSSwapBigToHostInt16(x)
#define le16toh(x) OSSwapLittleToHostInt16(x)
#define htobe32(x) OSSwapHostToBigInt32(x)
#define htole32(x) OSSwapHostToLittleInt32(x)
#define be32toh(x) OSSwapBigToHostInt32(x)
#define le32toh(x) OSSwapLittleToHostInt32(x)
#define htobe64(x) OSSwapHostToBigInt64(x)
#define htole64(x) OSSwapHostToLittleInt64(x)
#define be64toh(x) OSSwapBigToHostInt64(x)
#define le64toh(x) OSSwapLittleToHostInt64(x)
#else
#include <endian.h>
#endif
#include <stdint.h>
#include <libusockets.h>
int SSL;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Whatever type we selected (compressed or not) */
unsigned char *web_socket_request;
int web_socket_request_size;
char *upgrade_request;
int upgrade_request_length;
/* Compressed message */
unsigned char web_socket_request_deflate[13] = {
130 | 64, 128 | 7,
0, 0, 0, 0,
0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00
};
/* Not compressed */
unsigned char web_socket_request_text_small[26] = {130, 128 | 20, 1, 2, 3, 4};
unsigned int web_socket_request_text_size = 26;
unsigned char *web_socket_request_text = web_socket_request_text_small;
/* Called to swap from small text message to big text message */
void init_big_message(unsigned int size) {
if (size < 65536) {
printf("Error: message size must be bigger\n");
exit(0);
}
web_socket_request_text_size = size + 6 + 8;
web_socket_request_text = malloc(web_socket_request_text_size);
web_socket_request_text[0] = 130;
web_socket_request_text[1] = 255;
uint64_t msg_size = htobe64(size);
memcpy(&web_socket_request_text[2], &msg_size, 8);
web_socket_request_text[10] = 1;
web_socket_request_text[10] = 2;
web_socket_request_text[10] = 3;
web_socket_request_text[10] = 4;
}
void init_medium_message(unsigned int size) {
if (size > 65536) {
printf("Error: message size must be smaller\n");
exit(0);
}
web_socket_request_text_size = size + 6 + 2; // 8 for big
web_socket_request_text = malloc(web_socket_request_text_size);
web_socket_request_text[0] = 130;
web_socket_request_text[1] = 254;
uint16_t msg_size = htobe16(size);
memcpy(&web_socket_request_text[2], &msg_size, 2);
web_socket_request_text[4] = 1;
web_socket_request_text[5] = 2;
web_socket_request_text[6] = 3;
web_socket_request_text[7] = 4;
}
char request_deflate[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"
"Host: server.example.com\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
char request_text[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
//"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"
"Host: server.example.com\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
char *host;
int port;
int connections;
int responses;
struct http_socket {
/* How far we have streamed our websocket request */
int offset;
/* How far we have streamed our upgrade request */
int upgrade_offset;
/* Whether or not we have received the upgrade response */
int is_upgraded;
/* How many bytes we expect to be echoed back to us before we consider the echo done */
int outstanding_bytes;
};
/* We don't need any of these */
void on_wakeup(struct us_loop_t *loop) {
}
void on_pre(struct us_loop_t *loop) {
}
/* This is not HTTP POST, it is merely an event emitted post loop iteration */
void on_post(struct us_loop_t *loop) {
}
void next_connection(struct us_socket_t *s) {
/* We could wait with this until properly upgraded */
if (--connections) {
us_socket_context_connect(SSL, us_socket_context(SSL, s), host, port, NULL, 0, sizeof(struct http_socket));
} else {
printf("Running benchmark now...\n");
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
}
}
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Are we still not upgraded yet? */
if (http_socket->upgrade_offset < upgrade_request_length) {
http_socket->upgrade_offset += us_socket_write(SSL, s, upgrade_request + http_socket->upgrade_offset, upgrade_request_length - http_socket->upgrade_offset, 0);
/* Now we should be */
if (http_socket->upgrade_offset == upgrade_request_length) {
next_connection(s);
}
} else {
/* Stream whatever is remaining of the request */
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, web_socket_request_size - http_socket->offset, 0);
}
return s;
}
struct us_socket_t *on_http_socket_close(struct us_socket_t *s, int code, void *reason) {
printf("Closed!\n");
return s;
}
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
return us_socket_close(SSL, s, 0, NULL);
}
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
/* Get socket extension and the socket's context's extension */
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
if (http_socket->is_upgraded) {
/* If we are upgraded we now count to see if we receive the corect echo */
http_socket->outstanding_bytes -= length;
if (http_socket->outstanding_bytes == 0) {
/* We got exactly the correct amount of bytes back, send another message */
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, web_socket_request_size, 0);
http_socket->outstanding_bytes = web_socket_request_size - 4;
/* Increase stats */
responses++;
} else if (http_socket->outstanding_bytes < 0) {
/* This should never happen */
printf("ERROR: outstanding bytes negative!");
exit(0);
}
} else {
/* We assume the last 4 bytes will be delivered in one chunk */
if (length >= 4 && memcmp(data + length - 4, "\r\n\r\n", 4) == 0) {
/* We are upgraded so start sending the message for echoing */
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, web_socket_request_size, 0);
/* Server will echo back the same message minus 4 bytes for mask */
http_socket->outstanding_bytes = web_socket_request_size - 4;
http_socket->is_upgraded = 1;
}
}
return s;
}
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Reset offsets */
http_socket->offset = 0;
http_socket->is_upgraded = 0;
/* Send an upgrade request */
http_socket->upgrade_offset = us_socket_write(SSL, s, upgrade_request, upgrade_request_length, 0);
if (http_socket->upgrade_offset == upgrade_request_length) {
next_connection(s);
}
return s;
}
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
/* Print current statistics */
printf("Msg/sec: %f\n", ((float)responses) / LIBUS_TIMEOUT_GRANULARITY);
responses = 0;
us_socket_timeout(SSL, s, LIBUS_TIMEOUT_GRANULARITY);
return s;
}
int main(int argc, char **argv) {
/* Parse host and port */
if (argc != 6 && argc != 7) {
printf("Usage: connections host port ssl deflate [size_kb]\n");
return 0;
}
port = atoi(argv[3]);
host = malloc(strlen(argv[2]) + 1);
memcpy(host, argv[2], strlen(argv[2]) + 1);
connections = atoi(argv[1]);
SSL = atoi(argv[4]);
if (atoi(argv[5])) {
/* Set up deflate */
web_socket_request = web_socket_request_deflate;
web_socket_request_size = sizeof(web_socket_request_deflate);
upgrade_request = request_deflate;
upgrade_request_length = sizeof(request_deflate) - 1;
} else {
/* Only if we are NOT using defalte can we support testing with 100mb for now */
if (argc == 7) {
int size_kb = atoi(argv[6]);
printf("Using message size of %d kB\n", size_kb);
/* Size has to be in KB since the minimal size for medium is 1kb */
if (size_kb <= 64) {
init_medium_message(size_kb * 1024);
} else {
init_big_message(size_kb * 1024);
}
}
web_socket_request = web_socket_request_text;
web_socket_request_size = web_socket_request_text_size;
upgrade_request = request_text;
upgrade_request_length = sizeof(request_text) - 1;
}
/* Create the event loop */
struct us_loop_t *loop = us_create_loop(0, on_wakeup, on_pre, on_post, 0);
/* Create a socket context for HTTP */
struct us_socket_context_options_t options = {};
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
/* Set up event handlers */
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
/* Start making HTTP connections */
us_socket_context_connect(SSL, http_context, host, port, NULL, 0, sizeof(struct http_socket));
us_loop_run(loop);
}

View File

@@ -0,0 +1,276 @@
/* This is a scalability test for testing million(s) of pinging connections */
#include <libusockets.h>
int SSL;
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
unsigned char web_socket_request[26] = {130, 128 | 20, 1, 2, 3, 4};
char request[] = "GET / HTTP/1.1\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n"
"Host: server.example.com\r\n"
"Sec-WebSocket-Version: 13\r\n\r\n";
char *host;
int port;
int connections;
/* All the ips we as client can use */
char **ips;
int num_ips;
/* Send ping every 16 seconds */
int WEBSOCKET_PING_INTERVAL = 8;
/* We only establish 20k connections per address */
int CONNECTIONS_PER_ADDRESS = 20000;
/* How many connections a time */
int BATCH_CONNECT = 1;
/* Currently open and alive connections */
int opened_connections;
/* Dead connections */
int closed_connections;
struct http_socket {
/* How far we have streamed our websocket request */
int offset;
/* How far we have streamed our upgrade request */
int upgrade_offset;
};
struct us_socket_t *next_connection_failed = 0;
/* We don't need any of these */
void on_pre(struct us_loop_t *loop) {
}
/* This is not HTTP POST, it is merely an event emitted post loop iteration */
void on_post(struct us_loop_t *loop) {
}
void next_connection(struct us_socket_t *s) {
/* We could wait with this until properly upgraded */
if (--connections/* > BATCH_CONNECT*/) {
/* Swap address */
int address = opened_connections / CONNECTIONS_PER_ADDRESS;
if (us_socket_context_connect(SSL, us_socket_context(SSL, s), host, port, ips[address], 0, sizeof(struct http_socket)) == 0) {
printf("Next connection failed immediately\n");
/* Try agsin next event loop iteration */
next_connection_failed = s;
us_wakeup_loop(us_socket_context_loop(0, us_socket_context(0, s)));
}
}
}
void on_wakeup(struct us_loop_t *loop) {
if (next_connection_failed) {
struct us_socket_t *s = next_connection_failed;
next_connection_failed = 0;
next_connection(s);
}
}
struct us_socket_t *on_http_socket_writable(struct us_socket_t *s) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Are we still not upgraded yet? */
if (http_socket->upgrade_offset < sizeof(request) - 1) {
http_socket->upgrade_offset += us_socket_write(SSL, s, request + http_socket->upgrade_offset, sizeof(request) - 1 - http_socket->upgrade_offset, 0);
/* Now we should be */
if (http_socket->upgrade_offset == sizeof(request) - 1) {
next_connection(s);
/* Make sure to send ping */
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
}
} else {
/* Stream whatever is remaining of the request */
http_socket->offset += us_socket_write(SSL, s, (char *) web_socket_request + http_socket->offset, sizeof(web_socket_request) - http_socket->offset, 0);
if (http_socket->offset == sizeof(web_socket_request)) {
/* Reset timeout if we managed to */
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
}
}
return s;
}
struct us_socket_t *on_http_socket_close(struct us_socket_t *s, int code, void *reason) {
closed_connections++;
if (closed_connections % 1000 == 0) {
printf("Alive: %d, dead: %d\n", opened_connections, closed_connections);
}
return s;
}
struct us_socket_t *on_http_socket_end(struct us_socket_t *s) {
return us_socket_close(SSL, s, 0, NULL);
}
// should never get a response!
struct us_socket_t *on_http_socket_data(struct us_socket_t *s, char *data, int length) {
// is this a broadcasted unix time in millis?
if (length % 10 == 0) {
// data sent first will come first, so it is oldest
struct timespec ts;
timespec_get(&ts, TIME_UTC);
int64_t millis = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
int64_t received_millis;
memcpy(&received_millis, data + 2, 8);
static int counter;
static int max_latency;
static long average_latency;
int latency = millis - received_millis;
average_latency += latency;
if (max_latency < latency) {
max_latency = latency;
}
if (++counter % 10000 == 0) {
printf("Alive: %d, dead: %d\n", opened_connections, closed_connections);
printf("Max latency: %d ms\n", max_latency);
printf("Average latency: %ld ms\n\n", average_latency / 10000);
max_latency = 0;
average_latency = 0;
}
}
return s;
}
struct us_socket_t *on_http_socket_open(struct us_socket_t *s, int is_client, char *ip, int ip_length) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Display number of opened connections */
opened_connections++;
if (opened_connections % 1000 == 0) {
printf("Alive: %d, dead: %d\n", opened_connections, closed_connections);
}
/* Send an upgrade request */
http_socket->upgrade_offset = us_socket_write(SSL, s, request, sizeof(request) - 1, 0);
if (http_socket->upgrade_offset == sizeof(request) - 1) {
next_connection(s);
/* Make sure to send ping */
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
}
return s;
}
// here we should send a message as ping (part of the test)
struct us_socket_t *on_http_socket_timeout(struct us_socket_t *s) {
struct http_socket *http_socket = (struct http_socket *) us_socket_ext(SSL, s);
/* Send ping here */
http_socket->offset = us_socket_write(SSL, s, (char *) web_socket_request, sizeof(web_socket_request), 0);
if (http_socket->offset == sizeof(web_socket_request)) {
/* Reset timeout if we managed to */
us_socket_timeout(SSL, s, WEBSOCKET_PING_INTERVAL);
}
return s;
}
struct us_socket_t *on_http_socket_connect_error(struct us_socket_t *s, int code) {
printf("Connection failed\n");
next_connection(s);
return s;
}
int main(int argc, char **argv) {
/* Parse host and port */
if (argc < 5) {
printf("Usage: connections host port ssl [ip ...]\n");
return 0;
}
port = atoi(argv[3]);
host = malloc(strlen(argv[2]) + 1);
memcpy(host, argv[2], strlen(argv[2]) + 1);
connections = atoi(argv[1]);
SSL = atoi(argv[4]);
/* Do we have ip addresses? */
if (argc > 5) {
ips = &argv[5];
num_ips = argc - 5;
for (int i = 0; i < num_ips; i++) {
printf("%s\n", ips[i]);
}
} else {
static char *default_ips[] = {""};
ips = default_ips;
num_ips = 1;
}
/* Check so that we have enough ip addresses */
if (num_ips <= connections / CONNECTIONS_PER_ADDRESS) {
printf("You'll need more IP addresses for this run\n");
return 0;
}
/* Create the event loop */
struct us_loop_t *loop = us_create_loop(0, on_wakeup, on_pre, on_post, 0);
/* Create a socket context for HTTP */
struct us_socket_context_options_t options = {};
struct us_socket_context_t *http_context = us_create_socket_context(SSL, loop, 0, options);
/* Set up event handlers */
us_socket_context_on_open(SSL, http_context, on_http_socket_open);
us_socket_context_on_data(SSL, http_context, on_http_socket_data);
us_socket_context_on_writable(SSL, http_context, on_http_socket_writable);
us_socket_context_on_close(SSL, http_context, on_http_socket_close);
us_socket_context_on_timeout(SSL, http_context, on_http_socket_timeout);
us_socket_context_on_end(SSL, http_context, on_http_socket_end);
us_socket_context_on_connect_error(SSL, http_context, on_http_socket_connect_error);
/* Start making HTTP connections */
for (int i = 0; i < BATCH_CONNECT; i++) {
if (us_socket_context_connect(SSL, http_context, host, port, ips[0], 0, sizeof(struct http_socket)) == 0) {
printf("Connection failed immediately\n");
return 0;
}
}
us_loop_run(loop);
}

91
packages/bun-uws/build.c Normal file
View File

@@ -0,0 +1,91 @@
#include "build.h"
int main(int argc, char **argv) {
/* Some variables we need */
char *CXXFLAGS = strcpy(calloc(1024, 1), maybe(getenv("CXXFLAGS")));
char *CFLAGS = strcpy(calloc(1024, 1), maybe(getenv("CFLAGS")));
char *LDFLAGS = strcpy(calloc(1024, 1), maybe(getenv("LDFLAGS")));
char *CC = strcpy(calloc(1024, 1), or_else(getenv("CC"), "cc"));
char *CXX = strcpy(calloc(1024, 1), or_else(getenv("CXX"), "g++"));
char *EXEC_SUFFIX = strcpy(calloc(1024, 1), maybe(getenv("EXEC_SUFFIX")));
char *EXAMPLE_FILES[] = {"Http3Server", "Broadcast", "HelloWorld", "Crc32", "ServerName",
"EchoServer", "BroadcastingEchoServer", "UpgradeSync", "UpgradeAsync"};
strcat(CXXFLAGS, " -O3 -Wpedantic -Wall -Wextra -Wsign-conversion -Wconversion -std=c++20 -Isrc -IuSockets/src");
strcat(LDFLAGS, " uSockets/*.o");
// By default we use LTO, but Windows does not support it
if (!env_is("WITH_LTO", "0")) {
strcat(CXXFLAGS, " -flto");
}
// By default we use zlib but you can build without it (disables permessage-deflate)
if (!env_is("WITH_ZLIB", "0")) {
strcat(LDFLAGS, " -lz");
} else {
strcat(CXXFLAGS, " -DUWS_NO_ZLIB");
}
// WITH_PROXY enables PROXY Protocol v2 support
if (env_is("WITH_PROXY", "1")) {
strcat(CXXFLAGS, " -DUWS_WITH_PROXY");
}
// WITH_QUIC enables experimental Http3 examples
if (env_is("WITH_QUIC", "1")) {
strcat(CXXFLAGS, " -DLIBUS_USE_QUIC");
strcat(LDFLAGS, " -pthread -lz -lm uSockets/lsquic/src/liblsquic/liblsquic.a");
}
// Heavily prefer boringssl over openssl
if (env_is("WITH_BORINGSSL", "1")) {
strcat(CFLAGS, " -I uSockets/boringssl/include -pthread -DLIBUS_USE_OPENSSL");
strcat(LDFLAGS, " -pthread uSockets/boringssl/build/ssl/libssl.a uSockets/boringssl/build/crypto/libcrypto.a");
} else {
// WITH_OPENSSL=1 enables OpenSSL 1.1+ support
if (env_is("WITH_OPENSSL", "1")) {
// With problems on macOS, make sure to pass needed LDFLAGS required to find these
strcat(LDFLAGS, " -lssl -lcrypto");
} else {
// WITH_WOLFSSL=1 enables WolfSSL 4.2.0 support (mutually exclusive with OpenSSL)
if (env_is("WITH_WOLFSSL", "1")) {
strcat(LDFLAGS, " -L/usr/local/lib -lwolfssl");
}
}
}
// WITH_LIBUV=1 builds with libuv as event-loop
if (env_is("WITH_LIBUV", "1")) {
strcat(LDFLAGS, " -luv");
}
// WITH_ASIO=1 builds with ASIO as event-loop
if (env_is("WITH_ASIO", "1")) {
strcat(CXXFLAGS, " -pthread");
strcat(LDFLAGS, " -lpthread");
}
// WITH_ASAN builds with sanitizers
if (env_is("WITH_ASAN", "1")) {
strcat(CXXFLAGS, " -fsanitize=address -g");
strcat(LDFLAGS, " -lasan");
}
if (!strcmp(argv[1], "examples")) {
for (int i = 0; i < sizeof(EXAMPLE_FILES) / sizeof(char *); i++) {
if (run("%s%s examples/%s.cpp %s -o %s%s", CXX, CXXFLAGS, EXAMPLE_FILES[i], LDFLAGS, EXAMPLE_FILES[i], EXEC_SUFFIX)) {
return -1;
}
}
} else if (!strcmp(argv[1], "capi")) {
printf("capi target does nothing yet\n");
} else if (!strcmp(argv[1], "clean")) {
printf("clean target does nothing yet\n");
} else if (!strcmp(argv[1], "install")) {
// install target is not even supposed to be cross platform
printf("install target does nothing yet\n");
} else if (!strcmp(argv[1], "all")) {
printf("all target does nothing yet\n");
}
}

28
packages/bun-uws/build.h Normal file
View File

@@ -0,0 +1,28 @@
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
int env_is(char *env, char *target) {
char *val = getenv(env);
return val && !strcmp(val, target);
}
char *maybe(char *in) {
return in ? in : "";
}
char *or_else(char *in, char *otherwise) {
return in ? in : otherwise;
}
int run(const char *cmd, ...) {
char buf[2048];
va_list args;
va_start(args, cmd);
vsprintf(buf, cmd, args);
va_end(args);
printf("--> %s\n\n", buf);
return system(buf);
}

View File

@@ -0,0 +1,38 @@
CAPI_EXAMPLE_FILES := HelloWorld HelloWorldAsync ServerName UpgradeSync UpgradeAsync EchoServer Broadcast BroadcastEchoServer
RUST_EXAMPLE_FILES := RustHelloWorld
LIBRARY_NAME := libuwebsockets
default:
$(MAKE) capi
$(CXX) -O3 -flto -I ../src -I ../uSockets/src examples/HelloWorld.c *.o -lz -luv -lssl -lcrypto -lstdc++ ../uSockets/uSockets.a -o HelloWorld
capi:
$(MAKE) clean
cd ../uSockets && $(CC) -pthread -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -std=c11 -Isrc -flto -fPIC -O3 -c src/*.c src/eventing/*.c src/crypto/*.c
cd ../uSockets && $(CXX) -std=c++17 -flto -fPIC -O3 -c src/crypto/*.cpp
cd ../uSockets && $(AR) rvs uSockets.a *.o
$(CXX) -DUWS_WITH_PROXY -c -O3 -std=c++17 -lz -luv -flto -fPIC -I ../src -I ../uSockets/src $(LIBRARY_NAME).cpp
$(AR) rvs $(LIBRARY_NAME).a $(LIBRARY_NAME).o ../uSockets/uSockets.a
shared:
$(MAKE) clean
cd ../uSockets && $(CC) -pthread -DUWS_WITH_PROXY -DLIBUS_USE_OPENSSL -DLIBUS_USE_LIBUV -std=c11 -Isrc -flto -fPIC -O3 -c src/*.c src/eventing/*.c src/crypto/*.c
cd ../uSockets && $(CXX) -std=c++17 -flto -fPIC -O3 -c src/crypto/*.cpp
cd ../uSockets && $(AR) rvs uSockets.a *.o
$(CXX) -DUWS_WITH_PROXY -c -O3 -std=c++17 -lz -luv -flto -fPIC -I ../src -I ../uSockets/src $(LIBRARY_NAME).cpp
$(CXX) -shared -o $(LIBRARY_NAME).so $(LIBRARY_NAME).o ../uSockets/uSockets.a -fPIC -lz -luv -lssl -lcrypto
misc:
mkdir -p ../misc && openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -passout pass:1234 -keyout ../misc/key.pem -out ../misc/cert.pem
rust:
$(MAKE) capi
rustc -C link-arg=$(LIBRARY_NAME).a -C link-args="-lstdc++ -luv" -C opt-level=3 -C lto -L all=. examples/RustHelloWorld.rs -o RustHelloWorld
clean:
rm -f *.o $(CAPI_EXAMPLE_FILES) $(RUST_EXAMPLE_FILES) $(LIBRARY_NAME).a $(LIBRARY_NAME).so
all:
for FILE in $(CAPI_EXAMPLE_FILES); do $(CXX) -O3 -flto -I ../src -I ../uSockets/src examples/$$FILE.c *.o -luv -lstdc++ ../uSockets/uSockets.a -o $$FILE & done; \
wait

View File

@@ -0,0 +1,157 @@
#include "../libuwebsockets.h"
#include <stdio.h>
#include <malloc.h>
#include <time.h>
#include <string.h>
#include <stdarg.h>
#define SSL 1
//Timer close helper
void uws_timer_close(struct us_timer_t *timer)
{
struct us_timer_t *t = (struct us_timer_t *)timer;
struct timer_handler_data *data;
memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *));
free(data);
us_timer_close(t);
}
//Timer create helper
struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void *data), void *data)
{
struct us_loop_t *loop = uws_get_loop();
struct us_timer_t *delayTimer = us_create_timer(loop, 0, sizeof(void *));
struct timer_handler_data
{
void *data;
void (*handler)(void *data);
bool repeat;
};
struct timer_handler_data *timer_data = (struct timer_handler_data *)malloc(sizeof(timer_handler_data));
timer_data->data = data;
timer_data->handler = handler;
timer_data->repeat = repeat_ms > 0;
memcpy(us_timer_ext(delayTimer), &timer_data, sizeof(struct timer_handler_data *));
us_timer_set(
delayTimer, [](struct us_timer_t *t)
{
/* We wrote the pointer to the timer's extension */
struct timer_handler_data *data;
memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *));
data->handler(data->data);
if (!data->repeat)
{
free(data);
us_timer_close(t);
}
},
ms, repeat_ms);
return (struct us_timer_t *)delayTimer;
}
/* This is a simple WebSocket "sync" upgrade example.
* You may compile it with "WITH_OPENSSL=1 make" or with "make" */
/* ws->getUserData returns one of these */
struct PerSocketData {
/* Fill with user data */
};
int buffer_size(const char* format, ...) {
va_list args;
va_start(args, format);
int result = vsnprintf(NULL, 0, format, args);
va_end(args);
return result + 1; // safe byte for \0
}
void listen_handler(struct us_listen_socket_t *listen_socket, uws_app_listen_config_t config, void* user_data)
{
if (listen_socket){
printf("Listening on port wss://localhost:%d\n", config.port);
}
}
void open_handler(uws_websocket_t* ws){
/* Open event here, you may access uws_ws_get_user_data(WS) which points to a PerSocketData struct */
uws_ws_subscribe(SSL, ws, "broadcast", 9);
}
void message_handler(uws_websocket_t* ws, const char* message, size_t length, uws_opcode_t opcode){
}
void close_handler(uws_websocket_t* ws, int code, const char* message, size_t length){
/* You may access uws_ws_get_user_data(ws) here, but sending or
* doing any kind of I/O with the socket is not valid. */
}
void drain_handler(uws_websocket_t* ws){
/* Check uws_ws_get_buffered_amount(ws) here */
}
void ping_handler(uws_websocket_t* ws, const char* message, size_t length){
/* You don't need to handle this one, we automatically respond to pings as per standard */
}
void pong_handler(uws_websocket_t* ws, const char* message, size_t length){
/* You don't need to handle this one either */
}
void on_timer_interval(void* data){
// broadcast the unix time as millis
uws_app_t * app = (uws_app_t *)data;
struct timespec ts;
timespec_get(&ts, TIME_UTC);
int64_t millis = ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
char* message = (char*)malloc((size_t)buffer_size("%ld", millis));
size_t message_length = sprintf(message, "%ld", millis);
uws_publish(SSL, app, "broadcast", 9, message, message_length, uws_opcode_t::TEXT, false);
free(message);
}
int main()
{
uws_app_t *app = uws_create_app(SSL, (struct us_socket_context_options_t){
/* There are example certificates in uWebSockets.js repo */
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
});
uws_ws(SSL, app, "/*", (uws_socket_behavior_t){
.compression = uws_compress_options_t::SHARED_COMPRESSOR,
.maxPayloadLength = 16 * 1024,
.idleTimeout = 12,
.maxBackpressure = 1 * 1024 * 1024,
.upgrade = NULL,
.open = open_handler,
.message = message_handler,
.drain = drain_handler,
.ping = ping_handler,
.pong = pong_handler,
.close = close_handler,
});
uws_app_listen(SSL, app, 9001, listen_handler, NULL);
// broadcast the unix time as millis every 8 millis
uws_create_timer(8, 8, on_timer_interval, app);
uws_app_run(SSL, app);
}

View File

@@ -0,0 +1,175 @@
#include "../libuwebsockets.h"
#include <stdio.h>
#include <malloc.h>
#include <time.h>
#include <string.h>
#include <stdarg.h>
#define SSL 1
/* This is a simple WebSocket "sync" upgrade example.
* You may compile it with "WITH_OPENSSL=1 make" or with "make" */
typedef struct
{
size_t length;
char *name;
} topic_t;
/* ws->getUserData returns one of these */
struct PerSocketData
{
/* Fill with user data */
topic_t **topics;
int topics_quantity;
int nr;
};
uws_app_t *app;
int buffer_size(const char *format, ...)
{
va_list args;
va_start(args, format);
int result = vsnprintf(NULL, 0, format, args);
va_end(args);
return result + 1; // safe byte for \0
}
void listen_handler(struct us_listen_socket_t *listen_socket, uws_app_listen_config_t config, void* user_data)
{
if (listen_socket)
{
printf("Listening on port wss://localhost:%d\n", config.port);
}
}
void upgrade_handler(uws_res_t *response, uws_req_t *request, uws_socket_context_t *context)
{
/* You may read from req only here, and COPY whatever you need into your PerSocketData.
* PerSocketData is valid from .open to .close event, accessed with uws_ws_get_user_data(ws).
* HttpRequest (req) is ONLY valid in this very callback, so any data you will need later
* has to be COPIED into PerSocketData here. */
/* Immediately upgrading without doing anything "async" before, is simple */
struct PerSocketData *data = (struct PerSocketData *)malloc(sizeof(struct PerSocketData));
data->topics = (topic_t **)calloc(32, sizeof(topic_t *));
data->topics_quantity = 32;
data->nr = 0;
const char *ws_key = NULL;
const char *ws_protocol = NULL;
const char *ws_extensions = NULL;
size_t ws_key_length = uws_req_get_header(request, "sec-websocket-key", 17, &ws_key);
size_t ws_protocol_length = uws_req_get_header(request, "sec-websocket-protocol", 22, &ws_protocol);
size_t ws_extensions_length = uws_req_get_header(request, "sec-websocket-extensions", 24, &ws_extensions);
uws_res_upgrade(SSL,
response,
(void *)data,
ws_key,
ws_key_length,
ws_protocol,
ws_protocol_length,
ws_extensions,
ws_extensions_length,
context);
}
void open_handler(uws_websocket_t *ws)
{
/* Open event here, you may access uws_ws_get_user_data(ws) which points to a PerSocketData struct */
struct PerSocketData *data = (struct PerSocketData *)uws_ws_get_user_data(SSL, ws);
for (int i = 0; i < data->topics_quantity; i++)
{
char *topic = (char *)malloc((size_t)buffer_size("%ld-%d", (uintptr_t)ws, i));
size_t topic_length = sprintf(topic, "%ld-%d", (uintptr_t)ws, i);
topic_t *new_topic = (topic_t*) malloc(sizeof(topic_t));
new_topic->length = topic_length;
new_topic->name = topic;
data->topics[i] = new_topic;
uws_ws_subscribe(SSL, ws, topic, topic_length);
}
}
void message_handler(uws_websocket_t *ws, const char *message, size_t length, uws_opcode_t opcode)
{
struct PerSocketData *data = (struct PerSocketData *)uws_ws_get_user_data(SSL, ws);
topic_t *topic = data->topics[(size_t)(++data->nr % data->topics_quantity)];
uws_publish(SSL, app, topic->name, topic->length, message, length, opcode, false);
topic = data->topics[(size_t)(++data->nr % data->topics_quantity)];
uws_ws_publish(SSL, ws, topic->name, topic->length, message, length);
}
void close_handler(uws_websocket_t *ws, int code, const char *message, size_t length)
{
/* You may access uws_ws_get_user_data(ws) here, but sending or
* doing any kind of I/O with the socket is not valid. */
struct PerSocketData *data = (struct PerSocketData *)uws_ws_get_user_data(SSL, ws);
if (data)
{
for (int i = 0; i < data->topics_quantity; i++)
{
topic_t* topic = data->topics[i];
free(topic->name);
free(topic);
}
free(data->topics);
free(data);
}
}
void drain_handler(uws_websocket_t *ws)
{
/* Check uws_ws_get_buffered_amount(ws) here */
}
void ping_handler(uws_websocket_t *ws, const char *message, size_t length)
{
/* You don't need to handle this one, we automatically respond to pings as per standard */
}
void pong_handler(uws_websocket_t *ws, const char *message, size_t length)
{
/* You don't need to handle this one either */
}
int main()
{
uws_app_t *app = uws_create_app(SSL, (struct us_socket_context_options_t){
/* There are example certificates in uWebSockets.js repo */
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
});
uws_ws(SSL, app, "/*", (uws_socket_behavior_t){
.compression = uws_compress_options_t::SHARED_COMPRESSOR,
.maxPayloadLength = 16 * 1024,
.idleTimeout = 12,
.maxBackpressure = 1 * 1024 * 1024,
.upgrade = upgrade_handler,
.open = open_handler,
.message = message_handler,
.drain = drain_handler,
.ping = ping_handler,
.pong = pong_handler,
.close = close_handler,
});
uws_app_listen(SSL, app, 9001, listen_handler, NULL);
uws_app_run(SSL, app);
}

View File

@@ -0,0 +1,81 @@
#include "../libuwebsockets.h"
#include <stdio.h>
#include <malloc.h>
#define SSL 1
/* This is a simple WebSocket "sync" upgrade example.
* You may compile it with "WITH_OPENSSL=1 make" or with "make" */
/* ws->getUserData returns one of these */
struct PerSocketData {
/* Fill with user data */
};
void listen_handler(struct us_listen_socket_t *listen_socket, uws_app_listen_config_t config, void* user_data)
{
if (listen_socket){
printf("Listening on port wss://localhost:%d\n", config.port);
}
}
void open_handler(uws_websocket_t* ws){
/* Open event here, you may access uws_ws_get_user_data(WS) which points to a PerSocketData struct */
}
void message_handler(uws_websocket_t* ws, const char* message, size_t length, uws_opcode_t opcode){
uws_ws_send(SSL, ws, message, length, opcode);
}
void close_handler(uws_websocket_t* ws, int code, const char* message, size_t length){
/* You may access uws_ws_get_user_data(ws) here, but sending or
* doing any kind of I/O with the socket is not valid. */
}
void drain_handler(uws_websocket_t* ws){
/* Check uws_ws_get_buffered_amount(ws) here */
}
void ping_handler(uws_websocket_t* ws, const char* message, size_t length){
/* You don't need to handle this one, we automatically respond to pings as per standard */
}
void pong_handler(uws_websocket_t* ws, const char* message, size_t length){
/* You don't need to handle this one either */
}
int main()
{
uws_app_t *app = uws_create_app(SSL, (struct us_socket_context_options_t){
/* There are example certificates in uWebSockets.js repo */
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
});
uws_ws(SSL, app, "/*", (uws_socket_behavior_t){
.compression = uws_compress_options_t::SHARED_COMPRESSOR,
.maxPayloadLength = 16 * 1024,
.idleTimeout = 12,
.maxBackpressure = 1 * 1024 * 1024,
.upgrade = NULL,
.open = open_handler,
.message = message_handler,
.drain = drain_handler,
.ping = ping_handler,
.pong = pong_handler,
.close = close_handler,
});
uws_app_listen(SSL,app, 9001, listen_handler, NULL);
uws_app_run(SSL, app);
}

View File

@@ -0,0 +1,33 @@
#include "../libuwebsockets.h"
#include "libusockets.h"
#include <stdio.h>
#define SSL 1
void get_handler(uws_res_t *res, uws_req_t *req, void *user_data)
{
uws_res_end(SSL, res, "Hello CAPI!", 11, false);
}
void listen_handler(struct us_listen_socket_t *listen_socket, uws_app_listen_config_t config, void *user_data)
{
if (listen_socket)
{
printf("Listening on port https://localhost:%d now\n", config.port);
}
}
int main()
{
/* Overly simple hello world app */
uws_app_t *app = uws_create_app(SSL, (struct us_socket_context_options_t){
/* There are example certificates in uWebSockets.js repo */
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
});
uws_app_get(SSL, app, "/*", get_handler, NULL);
uws_app_listen(SSL, app, 3000, listen_handler, NULL);
uws_app_run(SSL, app);
}

View File

@@ -0,0 +1,123 @@
#include "../libuwebsockets.h"
#include "libusockets.h"
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#define SSL 0
typedef struct {
uws_res_t* res;
bool aborted;
} async_request_t;
//Timer close helper
void uws_timer_close(struct us_timer_t *timer)
{
struct us_timer_t *t = (struct us_timer_t *)timer;
struct timer_handler_data *data;
memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *));
free(data);
us_timer_close(t);
}
//Timer create helper
struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void *data), void *data)
{
struct us_loop_t *loop = uws_get_loop();
struct us_timer_t *delayTimer = us_create_timer(loop, 0, sizeof(void *));
struct timer_handler_data
{
void *data;
void (*handler)(void *data);
bool repeat;
};
struct timer_handler_data *timer_data = (struct timer_handler_data *)malloc(sizeof(timer_handler_data));
timer_data->data = data;
timer_data->handler = handler;
timer_data->repeat = repeat_ms > 0;
memcpy(us_timer_ext(delayTimer), &timer_data, sizeof(struct timer_handler_data *));
us_timer_set(
delayTimer, [](struct us_timer_t *t)
{
/* We wrote the pointer to the timer's extension */
struct timer_handler_data *data;
memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *));
data->handler(data->data);
if (!data->repeat)
{
free(data);
us_timer_close(t);
}
},
ms, repeat_ms);
return (struct us_timer_t *)delayTimer;
}
void on_res_aborted(uws_res_t *response, void* data){
async_request_t* request_data = (async_request_t*)data;
/* We don't implement any kind of cancellation here,
* so simply flag us as aborted */
request_data->aborted = true;
}
void on_res_corked(uws_res_t *response, void* data){
uws_res_end(SSL, response, "Hello CAPI!", 11, false);
}
void on_timer_done(void *data){
async_request_t* request_data = (async_request_t*)data;
/* Were'nt we aborted before our async task finished? Okay, send a message! */
if(!request_data->aborted){
uws_res_cork(SSL, request_data->res,on_res_corked, request_data);
}
}
void get_handler(uws_res_t *res, uws_req_t *req, void* user_data)
{
/* We have to attach an abort handler for us to be aware
* of disconnections while we perform async tasks */
async_request_t* request_data = (async_request_t*) malloc(sizeof(async_request_t));
request_data->res = res;
request_data->aborted = false;
uws_res_on_aborted(SSL, res, on_res_aborted, request_data);
/* Simulate checking auth for 5 seconds. This looks like crap, never write
* code that utilize us_timer_t like this; they are high-cost and should
* not be created and destroyed more than rarely!
* Either way, here we go!*/
uws_create_timer(1, 0, on_timer_done, request_data);
}
void listen_handler(struct us_listen_socket_t *listen_socket, uws_app_listen_config_t config, void* user_data)
{
if (listen_socket)
{
printf("Listening on port https://localhost:%d now\n", config.port);
}
}
int main()
{
/* Overly simple hello world app with async response */
uws_app_t *app = uws_create_app(SSL, (struct us_socket_context_options_t){
/* There are example certificates in uWebSockets.js repo */
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
});
uws_app_get(SSL, app, "/*", get_handler, NULL);
uws_app_listen(SSL, app, 3000, listen_handler, NULL);
uws_app_run(SSL, app);
}

View File

@@ -0,0 +1,309 @@
/* automatically generated by rust-bindgen 0.59.2 */
use std::convert::TryInto;
use std::ffi::CString;
pub type SizeT = ::std::os::raw::c_ulong;
pub type WcharT = ::std::os::raw::c_uint;
#[repr(C)]
#[repr(align(16))]
#[derive(Debug, Copy, Clone)]
pub struct max_align_t {
pub __clang_max_align_nonce1: ::std::os::raw::c_longlong,
pub __bindgen_padding_0: u64,
pub __clang_max_align_nonce2: u128,
}
#[test]
fn bindgen_test_layout_max_align_t() {
assert_eq!(
::std::mem::size_of::<max_align_t>(),
32usize,
concat!("Size of: ", stringify!(max_align_t))
);
assert_eq!(
::std::mem::align_of::<max_align_t>(),
16usize,
concat!("Alignment of ", stringify!(max_align_t))
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<max_align_t>())).__clang_max_align_nonce1 as *const _ as usize
},
0usize,
concat!(
"Offset of field: ",
stringify!(max_align_t),
"::",
stringify!(__clang_max_align_nonce1)
)
);
assert_eq!(
unsafe {
&(*(::std::ptr::null::<max_align_t>())).__clang_max_align_nonce2 as *const _ as usize
},
16usize,
concat!(
"Offset of field: ",
stringify!(max_align_t),
"::",
stringify!(__clang_max_align_nonce2)
)
);
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct uws_app_s {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct uws_req_s {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct uws_res_s {
_unused: [u8; 0],
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct uws_app_listen_config_s {
port: ::std::os::raw::c_int,
host: *const ::std::os::raw::c_char,
options: ::std::os::raw::c_int,
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct us_socket_context_options_s {
key_file_name: *const ::std::os::raw::c_char,
cert_file_name: *const ::std::os::raw::c_char,
passphrase: *const ::std::os::raw::c_char,
dh_params_file_name: *const ::std::os::raw::c_char,
ca_file_name: *const ::std::os::raw::c_char,
ssl_prefer_low_memory_usage: ::std::os::raw::c_int,
}
pub type UwsAppListenConfigT = uws_app_listen_config_s;
pub type UsSocketContextOptionsT = us_socket_context_options_s;
pub struct UsSocketContextOptions<'a> {
key_file_name: &'a str,
cert_file_name: &'a str,
passphrase: &'a str,
dh_params_file_name: &'a str,
ca_file_name: &'a str,
ssl_prefer_low_memory_usage: i32,
}
pub type UwsAppT = uws_app_s;
pub type UwsReqT = uws_req_s;
pub type UwsResT = uws_res_s;
extern "C" {
pub fn uws_create_app(
ssl: ::std::os::raw::c_int,
options: UsSocketContextOptionsT,
) -> *mut UwsAppT;
pub fn uws_app_get(
ssl: ::std::os::raw::c_int,
app: *mut UwsAppT,
pattern: *const ::std::os::raw::c_char,
handler: ::std::option::Option<
unsafe extern "C" fn(
res: *mut UwsResT,
req: *mut UwsReqT,
user_data: *mut ::std::os::raw::c_void,
),
>,
user_data: *mut ::std::os::raw::c_void,
);
pub fn uws_app_run(ssl: ::std::os::raw::c_int, app: *mut UwsAppT);
pub fn uws_app_listen(
ssl: ::std::os::raw::c_int,
app: *mut UwsAppT,
port: ::std::os::raw::c_int,
handler: ::std::option::Option<
unsafe extern "C" fn(
listen_socket: *mut ::std::os::raw::c_void,
config: UwsAppListenConfigT,
user_data: *mut ::std::os::raw::c_void,
),
>,
user_data: *mut ::std::os::raw::c_void,
);
pub fn uws_res_end(
ssl: ::std::os::raw::c_int,
res: *mut UwsResT,
data: *const ::std::os::raw::c_char,
length: SizeT,
close_connection: bool,
);
}
pub struct AppResponse<const SSL: i32> {
native: *mut UwsResT,
}
pub struct AppRequest {
native: *mut UwsReqT,
}
impl AppRequest {
pub fn new(native: *mut UwsReqT) -> AppRequest {
AppRequest { native: native }
}
}
impl<const SSL: i32> AppResponse<SSL> {
pub fn new(native: *mut UwsResT) -> AppResponse<SSL> {
AppResponse::<SSL> { native: native }
}
fn end(self, message: &str) -> AppResponse<SSL> {
unsafe {
let c_message =
::std::ffi::CString::new(message).expect("Failed to create message CString");
//This will now const fold :/ performance impact needs refactor
uws_res_end(
SSL,
self.native,
c_message.as_ptr(),
message.len().try_into().unwrap(),
false,
);
}
self
}
}
pub type UwsMethodHandler<const SSL: i32> = fn(res: AppResponse<SSL>, req: AppRequest);
pub type UwsListenHandler =
fn(listen_socket: *mut ::std::os::raw::c_void, config: UwsAppListenConfigT);
pub struct TemplateApp<const SSL: i32> {
native: *mut UwsAppT,
}
extern "C" fn uws_generic_listen_handler(
listen_socket: *mut ::std::os::raw::c_void,
config: UwsAppListenConfigT,
user_data: *mut ::std::os::raw::c_void,
) {
unsafe {
let callback = &mut *(user_data as *mut UwsListenHandler);
callback(listen_socket, config);
}
}
extern "C" fn uws_generic_method_handler(
res: *mut UwsResT,
req: *mut UwsReqT,
user_data: *mut ::std::os::raw::c_void,
) {
unsafe {
let response = AppResponse::<0>::new(res);
let request = AppRequest::new(req);
let callback = &mut *(user_data as *mut UwsMethodHandler<0>);
callback(response, request);
}
}
extern "C" fn uws_ssl_generic_method_handler(
res: *mut UwsResT,
req: *mut UwsReqT,
user_data: *mut ::std::os::raw::c_void,
) {
unsafe {
let response = AppResponse::<1>::new(res);
let request = AppRequest::new(req);
let callback = &mut *(user_data as *mut UwsMethodHandler<1>);
callback(response, request);
}
}
impl<const SSL: i32> TemplateApp<SSL> {
pub fn new(config: UsSocketContextOptions) -> TemplateApp<SSL> {
unsafe {
let key_file_name_s =
CString::new(config.key_file_name).expect("Failed to create key_file_name CString");
let cert_file_name_s = CString::new(config.cert_file_name)
.expect("Failed to create cert_file_name CString");
let passphrase_s =
CString::new(config.passphrase).expect("Failed to create passphrase CString");
let dh_params_file_name_s = CString::new(config.dh_params_file_name)
.expect("Failed to create dh_params_file_name CString");
let ca_file_name_s =
CString::new(config.ca_file_name).expect("Failed to create ca_file_name CString");
let native_options = UsSocketContextOptionsT {
key_file_name: key_file_name_s.as_ptr(),
cert_file_name: cert_file_name_s.as_ptr(),
passphrase: passphrase_s.as_ptr(),
dh_params_file_name: dh_params_file_name_s.as_ptr(),
ca_file_name: ca_file_name_s.as_ptr(),
ssl_prefer_low_memory_usage: config.ssl_prefer_low_memory_usage,
};
TemplateApp::<SSL> {
native: uws_create_app(SSL, native_options),
}
}
}
pub fn get(self, route: &str, mut handler: UwsMethodHandler<SSL>) -> TemplateApp<SSL> {
unsafe {
let c_route = ::std::ffi::CString::new(route).expect("Failed to create route CString");
if SSL == 1 {
uws_app_get(
SSL,
self.native,
c_route.as_ptr(),
std::option::Option::Some(uws_ssl_generic_method_handler),
&mut handler as *mut _ as *mut ::std::os::raw::c_void,
);
} else {
uws_app_get(
SSL,
self.native,
c_route.as_ptr(),
std::option::Option::Some(uws_generic_method_handler),
&mut handler as *mut _ as *mut ::std::os::raw::c_void,
);
}
}
self
}
pub fn listen(self, port: i32, mut handler: UwsListenHandler) -> TemplateApp<SSL> {
unsafe {
uws_app_listen(
SSL,
self.native,
port,
::std::option::Option::Some(uws_generic_listen_handler),
&mut handler as *mut _ as *mut ::std::os::raw::c_void,
);
}
self
}
pub fn run(self) -> TemplateApp<SSL> {
unsafe {
uws_app_run(SSL, self.native);
}
self
}
}
pub type App = TemplateApp<0>;
pub type SSLApp = TemplateApp<1>;
fn main() {
let config = UsSocketContextOptions {
key_file_name: "../misc/key.pem",
cert_file_name: "../misc/cert.pem",
passphrase: "1234",
ca_file_name: "",
dh_params_file_name: "",
ssl_prefer_low_memory_usage: 0,
};
SSLApp::new(config)
.get("/", |res, _req| {
res.end("Hello Rust!");
})
.listen(3000, |_listen_socket, config| {
println!("Listening on port https://127.0.0.1:{}", config.port);
})
.run();
}

View File

@@ -0,0 +1,59 @@
#include "../libuwebsockets.h"
#include <stdio.h>
#include <string.h>
#define SSL 1
struct us_listen_socket_t *globalListenSocket;
uws_app_t *app;
void get_handler(uws_res_t *res, uws_req_t *req, void* user_data)
{
uws_res_end(SSL, res, "Hello CAPI!", 11, false);
}
void exit_handler(uws_res_t *res, uws_req_t *req, void* user_data)
{
uws_res_end(SSL, res, "Shutting down!",14, false);
/* We use this to check graceful closedown */
us_listen_socket_close(false, globalListenSocket);
}
void missing_server_name_handler(const char *hostname, void* user_data){
printf("We are missing server name: <%s>\n", hostname);
/* Assume it is localhost, so add it */
uws_add_server_name(SSL, app, "localhost");
}
void listen_handler(struct us_listen_socket_t *listen_socket, uws_app_listen_config_t config, void* user_data)
{
if (listen_socket){
printf("Listening on port https://localhost:%d\n", config.port);
globalListenSocket = listen_socket;
}else{
printf("Failed to listen on port https://localhost:%d\n", config.port);
}
}
int main()
{
/* Overly simple hello world app (SNI)*/
app = uws_create_app(SSL, (struct us_socket_context_options_t){
/* There are example certificates in uWebSockets.js repo */
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
});
uws_missing_server_name(SSL, app, missing_server_name_handler, NULL);
uws_app_get(SSL, app, "/*", get_handler, NULL);
uws_app_get(SSL, app, "/exit", exit_handler, NULL);
uws_app_listen(SSL, app, 3000, listen_handler, NULL);
/* Let's add a wildcard SNI to begin with */
uws_add_server_name(SSL, app, "*.google.*");
uws_app_run(SSL, app);
}

View File

@@ -0,0 +1,255 @@
#include "../libuwebsockets.h"
#include "libusockets.h"
#include <stdio.h>
#include <malloc.h>
#include <string.h>
/* This is a simple WebSocket "sync" upgrade example.
* You may compile it with "WITH_OPENSSL=1 make" or with "make" */
#define SSL 1
typedef struct
{
char *value;
size_t length;
} header_t;
struct PerSocketData
{
/* Define your user data */
int something;
};
struct UpgradeData
{
header_t *secWebSocketKey;
header_t *secWebSocketProtocol;
header_t *secWebSocketExtensions;
uws_socket_context_t *context;
uws_res_t *response;
bool aborted;
};
header_t *create_header(size_t length, const char* value)
{
header_t *header = (header_t *)malloc(sizeof(header_t));
if(length > 0){
header->value = (char *)calloc(sizeof(char), length);
header->length = length;
memcpy(header->value, value, length);
}else{
header->value = NULL;
header->length = 0;
}
return header;
}
void free_header(header_t *header)
{
free(header->value);
free(header);
}
void listen_handler(struct us_listen_socket_t *listen_socket, uws_app_listen_config_t config, void *user_data)
{
if (listen_socket)
{
printf("Listening on port wss://localhost:%d\n", config.port);
}
}
//Timer close helper
void uws_timer_close(struct us_timer_t *timer)
{
struct us_timer_t *t = (struct us_timer_t *)timer;
struct timer_handler_data *data;
memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *));
free(data);
us_timer_close(t);
}
//Timer create helper
struct us_timer_t *uws_create_timer(int ms, int repeat_ms, void (*handler)(void *data), void *data)
{
struct us_loop_t *loop = uws_get_loop();
struct us_timer_t *delayTimer = us_create_timer(loop, 0, sizeof(void *));
struct timer_handler_data
{
void *data;
void (*handler)(void *data);
bool repeat;
};
struct timer_handler_data *timer_data = (struct timer_handler_data *)malloc(sizeof(timer_handler_data));
timer_data->data = data;
timer_data->handler = handler;
timer_data->repeat = repeat_ms > 0;
memcpy(us_timer_ext(delayTimer), &timer_data, sizeof(struct timer_handler_data *));
us_timer_set(
delayTimer, [](struct us_timer_t *t)
{
/* We wrote the pointer to the timer's extension */
struct timer_handler_data *data;
memcpy(&data, us_timer_ext(t), sizeof(struct timer_handler_data *));
data->handler(data->data);
if (!data->repeat)
{
free(data);
us_timer_close(t);
}
},
ms, repeat_ms);
return (struct us_timer_t *)delayTimer;
}
void on_timer_done(void *data)
{
struct UpgradeData *upgrade_data = (struct UpgradeData *)data;
/* Were'nt we aborted before our async task finished? Okay, upgrade then! */
if (!upgrade_data->aborted)
{
struct PerSocketData *socket_data = (struct PerSocketData *)malloc(sizeof(struct PerSocketData));
socket_data->something = 15;
printf("Async task done, upgrading to WebSocket now!\n");
uws_res_upgrade(SSL,
upgrade_data->response,
(void *)socket_data,
upgrade_data->secWebSocketKey->value,
upgrade_data->secWebSocketKey->length,
upgrade_data->secWebSocketProtocol->value,
upgrade_data->secWebSocketProtocol->length,
upgrade_data->secWebSocketExtensions->value,
upgrade_data->secWebSocketExtensions->length,
upgrade_data->context);
}
else
{
printf("Async task done, but the HTTP socket was closed. Skipping upgrade to WebSocket!\n");
}
free_header(upgrade_data->secWebSocketKey);
free_header(upgrade_data->secWebSocketProtocol);
free_header(upgrade_data->secWebSocketExtensions);
free(upgrade_data);
}
void on_res_aborted(uws_res_t *response, void *data)
{
struct UpgradeData *upgrade_data = (struct UpgradeData *)data;
/* We don't implement any kind of cancellation here,
* so simply flag us as aborted */
upgrade_data->aborted = true;
}
void upgrade_handler(uws_res_t *response, uws_req_t *request, uws_socket_context_t *context)
{
/* HttpRequest (req) is only valid in this very callback, so we must COPY the headers
* we need later on while upgrading to WebSocket. You must not access req after first return.
* Here we create a heap allocated struct holding everything we will need later on. */
struct UpgradeData *data = (struct UpgradeData *)malloc(sizeof(struct UpgradeData));
data->aborted = false;
data->context = context;
data->response = response;
const char *ws_key = NULL;
const char *ws_protocol = NULL;
const char *ws_extensions = NULL;
size_t ws_key_length = uws_req_get_header(request, "sec-websocket-key", 17, &ws_key);
size_t ws_protocol_length = uws_req_get_header(request, "sec-websocket-protocol", 22, &ws_protocol);
size_t ws_extensions_length = uws_req_get_header(request, "sec-websocket-extensions", 24, &ws_extensions);
data->secWebSocketKey = create_header(ws_key_length, ws_key);
data->secWebSocketProtocol = create_header(ws_protocol_length, ws_protocol);
data->secWebSocketExtensions = create_header(ws_extensions_length, ws_extensions);
/* We have to attach an abort handler for us to be aware
* of disconnections while we perform async tasks */
uws_res_on_aborted(SSL, response, on_res_aborted, data);
/* Simulate checking auth for 5 seconds. This looks like crap, never write
* code that utilize us_timer_t like this; they are high-cost and should
* not be created and destroyed more than rarely!
* Either way, here we go!*/
uws_create_timer(5000, 0, on_timer_done, data);
}
void open_handler(uws_websocket_t *ws)
{
/* Open event here, you may access uws_ws_get_user_data(ws) which points to a PerSocketData struct.
* Here we simply validate that indeed, something == 15 as set in upgrade handler. */
struct PerSocketData *data = (struct PerSocketData *)uws_ws_get_user_data(SSL, ws);
data->something = 15;
printf("Something is: %d\n", data->something);
}
void message_handler(uws_websocket_t *ws, const char *message, size_t length, uws_opcode_t opcode)
{
/* We simply echo whatever data we get */
uws_ws_send(SSL, ws, message, length, opcode);
}
void close_handler(uws_websocket_t *ws, int code, const char *message, size_t length)
{
/* You may access uws_ws_get_user_data(ws) here, but sending or
* doing any kind of I/O with the socket is not valid. */
struct PerSocketData *data = (struct PerSocketData *)uws_ws_get_user_data(SSL, ws);
if (data)
{
free(data);
}
}
void drain_handler(uws_websocket_t *ws)
{
/* Check uws_ws_get_buffered_amount(ws) here */
}
void ping_handler(uws_websocket_t *ws, const char *message, size_t length)
{
/* You don't need to handle this one, we automatically respond to pings as per standard */
}
void pong_handler(uws_websocket_t *ws, const char *message, size_t length)
{
/* You don't need to handle this one either */
}
int main()
{
uws_app_t *app = uws_create_app(SSL, (struct us_socket_context_options_t){
/* There are example certificates in uWebSockets.js repo */
.key_file_name = "../misc/key.pem",
.cert_file_name = "../misc/cert.pem",
.passphrase = "1234"
});
uws_ws(SSL, app, "/*", (uws_socket_behavior_t){
.compression = uws_compress_options_t::SHARED_COMPRESSOR,
.maxPayloadLength = 16 * 1024,
.idleTimeout = 12,
.maxBackpressure = 1 * 1024 * 1024,
.upgrade = upgrade_handler,
.open = open_handler,
.message = message_handler,
.drain = drain_handler,
.ping = ping_handler,
.pong = pong_handler,
.close = close_handler,
});
uws_app_listen(SSL, app, 9001, listen_handler, NULL);
uws_app_run(SSL, app);
}

Some files were not shown because too many files have changed in this diff Show More